No description
  • Rust 95.3%
  • Shell 4.3%
  • Makefile 0.4%
Find a file
Jan De Landtsheer e9656e298b
Some checks failed
Integration Tests / integration (push) Failing after 1s
Add multi-user seeding and usage examples
Seeder now creates regular CRM users via admin auth and distributes
all assignable records (leads, opportunities, tasks, meetings, calls,
cases) randomly across them. Added base64 and dotenvy deps to seeder
feature gate, auto-loads .env.test, and grants User read permission
to the test role. Includes example session docs and MCP config.
2026-02-21 12:00:20 +01:00
.github/workflows Initial implementation of EspoCRM MCP server 2026-02-19 02:29:45 +01:00
docs Add comprehensive test environment setup guide 2026-02-21 10:48:53 +01:00
scripts Add multi-user seeding and usage examples 2026-02-21 12:00:20 +01:00
src Add multi-user seeding and usage examples 2026-02-21 12:00:20 +01:00
tests Remove unused test helpers and suppress false-positive dead_code warnings 2026-02-20 15:24:07 +01:00
.gitignore Add .espomcp/ to gitignore (runtime-generated prompt templates) 2026-02-21 10:49:47 +01:00
.mcp.json Add multi-user seeding and usage examples 2026-02-21 12:00:20 +01:00
Cargo.lock Add multi-user seeding and usage examples 2026-02-21 12:00:20 +01:00
Cargo.toml Add multi-user seeding and usage examples 2026-02-21 12:00:20 +01:00
docker-compose.yml Initial implementation of EspoCRM MCP server 2026-02-19 02:29:45 +01:00
example_session.md Add multi-user seeding and usage examples 2026-02-21 12:00:20 +01:00
examples.md Add multi-user seeding and usage examples 2026-02-21 12:00:20 +01:00
Makefile Add v2.0 write-mode implementation files 2026-02-20 12:34:20 +01:00
README.md Implement v2.0 gap fixes: file-based prompts, PRD compliance 2026-02-20 12:31:01 +01:00

espomcp

A Model Context Protocol (MCP) server for EspoCRM. Provides both read and write access to CRM data, plus agent workflow prompts. Enables AI assistants to discover entities, query records, create/update/delete data, manage relationships, and execute CRM workflows through a standardized protocol.

Built in Rust. Communicates over stdio using JSON-RPC.

Quick Start

# Build
cargo build --release

# Configure
export ESPOCRM_URL=https://your-instance.example.com
export ESPOCRM_API_KEY=your-api-key

# Run (stdio — intended to be launched by an MCP client)
./target/release/espomcp

EspoCRM Setup

Create an API User

The MCP server authenticates via API key. Create a dedicated API user in EspoCRM:

  1. Log into EspoCRM as admin
  2. Go to Administration > Users
  3. Click Create User, set Type to API
  4. Set Auth Method to API Key
  5. Save — EspoCRM auto-generates the API key
  6. Copy the API key for configuration

Assign Permissions

The API user has no access by default. Create a Role with appropriate permissions:

  1. Go to Administration > Roles
  2. Create a role with permissions on the entity types you need:
    • Read = All for entities you want to query
    • Create = Yes, Edit = All, Delete = All for entities the agent should modify
    • Stream = All for entities where you want to add notes
    • Important: Do NOT grant Email send permission — the server only creates drafts
  3. Assign the role to your API user

For automated setup (e.g., testing), see scripts/setup-test-user.sh.

Configuration

Configuration is loaded from three sources (highest priority first):

  1. Environment variables
  2. Config file (~/.config/espocrm-mcp/config.toml)
  3. Built-in defaults

Environment Variables

Variable Required Default Description
ESPOCRM_URL Yes EspoCRM instance URL
ESPOCRM_API_KEY Yes API authentication key
ESPOCRM_TIMEOUT_SECS No 30 HTTP request timeout
ESPOCRM_TLS_VERIFY No true Verify TLS certificates (set false for local dev)
SERVER_MAX_LIMIT No 200 Maximum records per query
SERVER_METADATA_TTL_SECS No 300 Schema/entity type cache TTL
SERVER_RATE_LIMIT_REQUESTS_PER_SECOND No 10 Rate limit
SERVER_RATE_LIMIT_BURST No 20 Burst allowance
SERVER_RATE_LIMIT_MAX_CONCURRENT No 5 Max concurrent requests
LOGGING_LEVEL No info Log level (error, warn, info, debug, trace)
LOGGING_JSON No false JSON-formatted log output

Config File

~/.config/espocrm-mcp/config.toml (or $XDG_CONFIG_HOME/espocrm-mcp/config.toml):

[espocrm]
url = "https://your-instance.example.com"
api_key = "your-api-key"
timeout_secs = 30
tls_verify = true

[server]
max_limit = 200
metadata_ttl_secs = 300

[server.rate_limit]
requests_per_second = 10
burst = 20
max_concurrent = 5

[logging]
level = "info"
json = false

If the config file is world-readable, the server logs a warning. Use chmod 600 to restrict access.

MCP Client Setup

The server is a stdio binary — the MCP client spawns it as a subprocess. Below are configurations for all major clients.

Claude Desktop

Edit the config file (or via Settings > Developer > Edit Config):

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "espocrm": {
      "command": "/path/to/espomcp",
      "args": [],
      "env": {
        "ESPOCRM_URL": "https://your-instance.example.com",
        "ESPOCRM_API_KEY": "your-api-key"
      }
    }
  }
}

Restart Claude Desktop after editing.

Claude Code

Option A: CLI command

claude mcp add --transport stdio \
  --env ESPOCRM_URL=https://your-instance.example.com \
  --env ESPOCRM_API_KEY=your-api-key \
  espocrm -- /path/to/espomcp

Add --scope user to make it available across all projects, or --scope project to create a shared .mcp.json.

Option B: Project .mcp.json (checked into repo, secrets via env var expansion)

{
  "mcpServers": {
    "espocrm": {
      "command": "/path/to/espomcp",
      "args": [],
      "env": {
        "ESPOCRM_URL": "${ESPOCRM_URL}",
        "ESPOCRM_API_KEY": "${ESPOCRM_API_KEY}"
      }
    }
  }
}

Useful commands:

claude mcp list              # List configured servers
claude mcp get espocrm       # Show server details
claude mcp remove espocrm    # Remove server

Use /mcp inside Claude Code to check server status.

Cursor

Create ~/.cursor/mcp.json (global) or .cursor/mcp.json (per-project):

{
  "mcpServers": {
    "espocrm": {
      "command": "/path/to/espomcp",
      "args": [],
      "env": {
        "ESPOCRM_URL": "https://your-instance.example.com",
        "ESPOCRM_API_KEY": "your-api-key"
      }
    }
  }
}

VS Code (GitHub Copilot)

Create .vscode/mcp.json in your workspace:

{
  "inputs": [
    {
      "type": "promptString",
      "id": "espocrm-api-key",
      "description": "EspoCRM API Key",
      "password": true
    }
  ],
  "servers": {
    "espocrm": {
      "type": "stdio",
      "command": "/path/to/espomcp",
      "args": [],
      "env": {
        "ESPOCRM_URL": "https://your-instance.example.com",
        "ESPOCRM_API_KEY": "${input:espocrm-api-key}"
      }
    }
  }
}

Note: VS Code uses servers (not mcpServers) and supports ${input:id} for secret prompting.

Windsurf (Codeium)

Edit ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "espocrm": {
      "command": "/path/to/espomcp",
      "args": [],
      "env": {
        "ESPOCRM_URL": "https://your-instance.example.com",
        "ESPOCRM_API_KEY": "your-api-key"
      }
    }
  }
}

Zed

Edit ~/.config/zed/settings.json (Linux) or ~/Library/Application Support/Zed/settings.json (macOS):

{
  "context_servers": {
    "espocrm": {
      "source": "custom",
      "command": "/path/to/espomcp",
      "args": [],
      "env": {
        "ESPOCRM_URL": "https://your-instance.example.com",
        "ESPOCRM_API_KEY": "your-api-key"
      }
    }
  }
}

Note: Zed uses context_servers with "source": "custom".

Continue.dev

Edit ~/.continue/config.yaml:

mcpServers:
  - name: espocrm
    command: /path/to/espomcp
    args: []
    env:
      ESPOCRM_URL: "https://your-instance.example.com"
      ESPOCRM_API_KEY: "your-api-key"

Client Config Summary

Client Config File Root Key
Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json mcpServers
Claude Code .mcp.json or claude mcp add mcpServers
Cursor ~/.cursor/mcp.json mcpServers
VS Code .vscode/mcp.json servers
Windsurf ~/.codeium/windsurf/mcp_config.json mcpServers
Zed ~/.config/zed/settings.json context_servers
Continue.dev ~/.continue/config.yaml mcpServers

Tools

The server exposes 13 tools: 5 read-only and 8 write tools.

Read Tools

list_entity_types

Discover all available entity types.

{}

get_schema

Get field definitions and relationship links for an entity type. Call this before create/update to discover required fields and valid enum values.

{ "entityType": "Contact" }

Query records with filters, sorting, and pagination.

{
  "entityType": "Opportunity",
  "where": [
    { "field": "stage", "type": "in", "value": ["Proposal", "Negotiation"] },
    { "field": "amount", "type": "greaterThan", "value": 10000 }
  ],
  "orderBy": "amount",
  "order": "desc",
  "limit": 20
}

Filter operators: equals, notEquals, like (% wildcards), greaterThan, lessThan, in (array), isNull, isNotNull, between (array of two values).

get

Retrieve a single record by ID.

{ "entityType": "Contact", "id": "abc123" }

Traverse relationships to get linked records.

{ "entityType": "Account", "id": "acct456", "link": "contacts", "limit": 20 }

Write Tools

create

Create a new record. Validates fields against schema. Cannot create Email records — use draft_email instead.

{
  "entityType": "Account",
  "data": { "name": "Acme Corp", "website": "https://acme.com" },
  "skipDuplicateCheck": false
}

update

Update an existing record's fields. Only include fields you want to change.

{
  "entityType": "Opportunity",
  "id": "opp123",
  "data": { "stage": "Closed Won" }
}

delete

Delete a record with mandatory two-step confirmation:

  1. Call with confirm: false — returns a preview of the record
  2. Call with confirm: true — actually deletes the record
{ "entityType": "Account", "id": "acct123", "confirm": true }

Link records via a named relationship. Provide foreignId (single) or foreignIds (multiple).

{
  "entityType": "Account",
  "id": "acct123",
  "link": "contacts",
  "foreignId": "cont456"
}

Remove a relationship link between records.

{
  "entityType": "Account",
  "id": "acct123",
  "link": "contacts",
  "foreignId": "cont456"
}

add_note

Post a text note to a record's activity stream.

{
  "entityType": "Account",
  "id": "acct123",
  "text": "Called and confirmed renewal for Q3."
}

update_kanban

Move a record to a different Kanban group (stage/status) and/or position.

{
  "entityType": "Opportunity",
  "id": "opp123",
  "group": "Proposal",
  "position": 0
}

draft_email

Create a draft email. Always creates as Draft — never sends. Optionally renders an email template.

{
  "to": "client@example.com",
  "subject": "Follow-up",
  "body": "Hi, just following up...",
  "isHtml": false,
  "templateId": "tpl123",
  "parentType": "Contact",
  "parentId": "cont456"
}

MCP Prompts

The server provides 6 workflow prompts that prime AI agents with CRM-specific instructions:

Prompt Arguments Description
process-call-transcript transcript (req), account_hint (opt), contact_hint (opt) Parse a call transcript, identify contacts, create tasks, update pipeline, draft follow-up
log-meeting notes (req), attendees (opt), date (opt), duration (opt) Create meeting record, link attendees, create action item tasks
process-inbound-lead details (req), source (opt) Deduplicate, create lead, assign follow-up task
draft-followup context (req), recipient (opt), template_name (opt) Draft a context-aware follow-up email for a CRM record
pipeline-update changes (req) Process batch pipeline stage changes
email-triage emails (req) Triage incoming email, identify sender, create tasks, draft response

All prompts include safety guardrails: never send emails (only drafts), never delete without confirmation, always verify before updating.

Customizing Prompts

On first use, the server writes default prompt templates to .espomcp/prompts/ in the working directory. Each prompt is stored as a separate .md file:

.espomcp/prompts/
  process-call-transcript.md
  log-meeting.md
  process-inbound-lead.md
  draft-followup.md
  pipeline-update.md
  email-triage.md

Edit any file to customize the system prompt for that workflow. Changes take effect on the next get_prompt call — no restart required. Delete a file to reset it to the built-in default on the next access.

Error Handling

All errors include a machine-readable code, human-readable message, and optional suggestion:

{
  "error": {
    "code": "INVALID_ENTITY_TYPE",
    "message": "Entity type 'BadType' not found",
    "suggestion": "Use list_entity_types to see available entities."
  }
}
Code Condition Retryable
INVALID_ENTITY_TYPE Unknown entity type No
INVALID_FIELD Unknown field on entity No
INVALID_OPERATOR Unsupported filter operator No
RECORD_NOT_FOUND ID does not exist No
INVALID_LINK Unknown relationship link No
AUTH_FAILED Invalid API key (401) No
PERMISSION_DENIED Insufficient permissions (403) No
RATE_LIMITED Too many requests (429) Yes
UPSTREAM_ERROR EspoCRM API error Yes
UPSTREAM_TIMEOUT Request timed out Yes
MISSING_REQUIRED_FIELD Required field not provided in create No
INVALID_FIELD_VALUE Wrong type or invalid enum value No
DELETE_NOT_CONFIRMED Delete called without confirm=true Yes
DUPLICATE_DETECTED Record already exists (HTTP 409) Yes
TEMPLATE_NOT_FOUND Email template ID not found No
SEND_BLOCKED Attempted to create Email via generic create tool No

Development

Prerequisites

  • Rust 1.85+ (edition 2024)
  • Podman or Docker (for integration tests)

Running Tests

# Unit tests only (no containers needed)
make test-unit

# Prompt tests (no containers needed)
make test-prompts

# Integration tests (requires running EspoCRM)
make test-setup          # Start EspoCRM containers + seed data
make test-read           # v1 read-only tests
make test-write          # v2 write tests
make test-integration    # All integration tests
make test-teardown       # Stop containers

# Full cycle
make test-full           # Setup -> all tests -> teardown

# Reset test data without restarting containers
make test-reset

Project Structure

src/
  main.rs              # Entry point — config, logging, stdio transport
  lib.rs               # Module declarations
  config.rs            # Figment-based config (env + TOML + defaults)
  client.rs            # EspoCRM HTTP client with caching and rate limiting
  server.rs            # MCP tool handlers (13 tools) + prompt support
  types.rs             # Domain types (EntityType, Schema, SearchParams, etc.)
  error.rs             # Error types with codes and suggestions (18 variants)
  cache.rs             # Metadata cache (entity types + schemas)
  rate_limit.rs        # Governor-based rate limiter
  validation.rs        # Input validation (entity type, ID, limit, offset)
  write_validation.rs  # Schema-aware write data validation
  prompts.rs           # 6 MCP prompt definitions and message builders
  redaction.rs         # PII redaction for TRACE-level logging
tests/
  integration.rs       # Read-only integration tests against live EspoCRM
  write_integration.rs # Write integration tests (create/update/delete/link)
  prompt_tests.rs      # Prompt definition and guardrail tests
  common/
    mod.rs             # Test helpers and client factory
    write_helpers.rs   # Admin client, RAII cleanup guards, write test context
scripts/               # Setup, teardown, and seeding scripts
docs/ai/               # PRD and Architecture Decision Records

License

See LICENSE for details.