KasarKasar Docs
MCP Server

Tools — Records

Core MCP tools for searching, creating, and updating CRM records, moving deals through pipelines, and managing duplicates.

The Records tools let your AI assistant read and write any CRM object — contacts, companies, opportunities, or any custom object you have defined — plus move deals through their pipeline and detect and merge duplicates.

Records can be deleted over MCP via crm_delete_records (soft-delete, max 50 per call, RBAC-guarded). Deletion is irreversible, so the assistant should re-read the records, show them to you, and delete only after explicit confirmation. To consolidate records instead of deleting, use crm_manage_duplicates.

crm_find_records

Search, list, or get CRM records on any object — full-text search, field filtering, sorting, grouping, and cursor pagination.

This tool has an attached MCP App: flat list results render as an interactive table / cards / kanban view in clients that support MCP Apps.

Parameters

ParameterTypeRequiredDescription
object_namestringYesObject name: contacts, companies, opportunities, or any custom object. Use kasar://schema to discover available objects and fields.
record_idstringNoGet a single record by UUID. Returns the augmented record (see below).
searchstringNoFull-text search across all text fields (min 2 chars). Not compatible with group_by.
filter_fieldstringNoField name for a single simple condition. For anything richer use filters.
filter_operatorenumNoOne of equals, not_equals, contains, starts_with, ends_with, greater_than, less_than, greater_equal, less_equal, is_null, is_not_null, in.
filter_valuestringNoValue to filter against. For in: comma-separated values.
filtersstringNoJSON-encoded FilterGroup for nested AND/OR logic. Takes precedence over filter_field/filter_operator/filter_value.
sort_bystringNoSingle sort field (default: created_at). For multi-column sort use sort.
sort_dirasc | descNoSort direction (default desc).
sortstringNoMulti-column sort as a JSON array: [{"field":"amount_amount","direction":"desc"},{"field":"name","direction":"asc"}]. Takes precedence over sort_by/sort_dir.
group_bystringNoGroup results into buckets by a SELECT/relation field, or a PIPELINE step column. Returns {groups, counts} instead of {data, total} — use it for kanban-style views. Not compatible with search.
limit_per_groupnumberNoMax rows per group when group_by is set (default 20, max 100).
cursorstringNoPagination cursor from a previous response. Ignored in grouped mode.
limitnumberNoResults per page (default 20, max 100).

The augmented record

Fetching by record_id returns the augmented record: the enriched record plus its list memberships.

  • Display values for foreign keys (e.g. entreprise_id__display → the company name).
  • Expanded compound fields (EMAILS, PHONES, ADDRESS, CURRENCY, …).
  • Many-to-many relations auto-enriched as related_to[] (and any other M2M field on the object).
  • A lists array of list memberships. Each entry carries the values of the list's per-entry fields (custom fields defined on the list itself, such as a review status or score):
{
  "record": { "id": "550e8400-…", "full_name": "Alice Martin" },
  "lists": [
    {
      "list_id": "a529e4c5-…",
      "list_name": "Salon 2026",
      "entry_id": "f3dcfe67-…",
      "entry_fields": { "added_at": "2026-06-11T16:57:09Z", "statut_revue": "valide" }
    }
  ]
}

lists is omitted when the record belongs to no list.

Examples

Get a contact by ID:

{
  "object_name": "contacts",
  "record_id": "550e8400-e29b-41d4-a716-446655440000"
}

Search contacts by name:

{
  "object_name": "contacts",
  "search": "Alice Martin",
  "limit": 10
}

Complex filter with sorting:

Find contacts with status "Lead" or "customer", sorted by name:

{
  "object_name": "contacts",
  "filters": "{\"type\":\"OR\",\"conditions\":[{\"field\":\"contact_status\",\"operator\":\"equals\",\"value\":\"Lead\"},{\"field\":\"contact_status\",\"operator\":\"equals\",\"value\":\"customer\"}]}",
  "sort": "[{\"field\":\"full_name\",\"direction\":\"asc\"}]"
}

Group opportunities by pipeline stage (kanban):

{
  "object_name": "opportunities",
  "group_by": "pipeline_step_id",
  "limit_per_group": 25
}

FilterGroup format

The filters parameter accepts a JSON-encoded FilterGroup that supports nested AND/OR logic.

{
  "type": "AND",
  "conditions": [
    { "field": "full_name", "operator": "contains", "value": "marc" },
    {
      "type": "OR",
      "conditions": [
        { "field": "contact_status", "operator": "equals", "value": "Lead" },
        { "field": "contact_status", "operator": "equals", "value": "customer" }
      ]
    }
  ]
}

This matches records where the name contains "marc" and the status is either "Lead" or "customer". Groups can be nested to any depth. In filters, operators also include not_in and between in addition to the simple ones above.

Response

A flat list returns { data, total, nextCursor }:

{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "full_name": "Alice Martin",
      "email": "alice@example.com",
      "contact_status": "customer",
      "created_at": "2025-01-15T10:30:00Z"
    }
  ],
  "total": 42,
  "nextCursor": "eyJpZCI6IjU1MGU4NDAw..."
}

When nextCursor is absent or null, you have reached the last page. Grouped mode returns { object_name, group_by, limit_per_group, groups, counts } instead.

To narrow the returned columns or export a slice as CSV/JSON, use crm_export_records instead — crm_find_records always returns the full enriched record.


crm_save_records

The upsert tool — create or update one or many records in a single call, including their relations and list memberships.

A record is "augmented" by three layers, and this one tool edits all three: its own fields (data), its many-to-many links and junction fields (relations), and its list memberships and per-entry fields (the list params).

Replaces the old crm_save_record (singular). Pass a single object for one record, or an array to upsert many. To set the same value across many records, or to bulk add/remove an M2M link, use crm_bulk_update_records instead.

Parameters

ParameterTypeRequiredDescription
object_namestringYesTarget object. Check kasar://schema/{object_name} for fields and relation write shapes.
dataobject | object[]YesA single record object, or an array of record objects for batch upsert (max 200 per call — a larger array is rejected with TOO_MANY_RECORDS; split into smaller batches). In array mode, put id (or record_id) in an element to update it; omit it to create.
record_idstringNoSingle mode only: UUID to update. Omit to create. Ignored when data is an array (use each element's id).
relationsobjectNoIncremental M2M edits keyed by field name: { fieldName: { add, remove, update } }. See below.
add_to_list_idsstring[]NoList UUIDs — adds the record(s) to these lists in the same call. Per-list outcome returned in lists.
create_list_namestringNoCreates a new list with this name (allowing this object) and adds the record(s) to it (admin only).
remove_from_list_idsstring[]NoList UUIDs — removes the record(s) from these lists. Outcome in removed_from_lists.
update_list_entriesobject[]NoEdit the per-entry fields of existing list memberships: [{ list_id, fields: { …entryFields } }]. Mirror of the read-side lists[].entry_fields. Outcome in updated_list_entries.

The relations delta object per field accepts:

KeyTypeDescription
addarrayLinks to create: [{ targetId, junctionData?, _objectType? }]. junctionData sets junction fields; _objectType is required for multi-target relations.
removestring[]Target UUIDs to unlink.
updatearrayPatch the junction fields of an existing link in place: [{ targetId, junctionData, _objectType? }] — without replacing the whole set.

Two ways to write many-to-many

  • Replace the whole set — set the field directly in data as an array. This drops any link you omit:
    { "data": { "companies": [{ "targetId": "uuid-company", "junctionData": { "position": "CEO" } }] } }
  • Edit incrementally — use relations[field] = { add, remove, update }. update patches the junctionData of an existing link in place without touching the others — use this to edit junction fields:
    { "relations": { "companies": { "update": [{ "targetId": "uuid-company", "junctionData": { "position": "CEO & Chairman" } }] } } }

Relation payloads use camelCase keys. Single-target M2M = [{ targetId, junctionData }]; multi-target M2M (e.g. tasks.related_to, notes.related_to) = [{ targetId, _objectType, junctionData }] where _objectType is required; BELONGS_TO_ONE = pass the FK UUID string directly in data; MORPH_RELATION = { id, _objectType }. Check each field's writeShape / junctionFields / multiTarget in kasar://schema/{object_name}.

Automatic behaviors on create

When creating a new record (no id/record_id), the server applies defaults automatically:

  • Pipeline defaults — if the object has a pipeline, the default pipeline and its first step are assigned.
  • USER fieldsowner_id and created_by_id are set to the authenticated user.
  • Field defaults — default values defined in field metadata are applied for any omitted fields.
  • Tasks and notes route through their canonical create actions, so a created tasks record fires the assignment notification + title-based entity auto-link, and a created notes record derives its title from the content.

Changing the pipeline step

Set the step column directly in data (e.g. pipeline_step_id, or the PIPELINE field name) to run the same canonical transition as crm_move_deal:

  • Required-field validation. If the source step has required_for_next_stage questions (forward transition) or required_for_closure questions (closure to a final step), the call is rejected with MISSING_REQUIRED_FIELDS when those linked fields are empty on the record.
  • Forced and default values configured on the target step are applied server-side.
  • Witness rows are recorded in pipeline_step_history so the step is tagged as visited.

Read kasar://pipelines first to discover which fields are required per step, then fill them in the same crm_save_records call as the step change — one payload that sets the required field AND moves the step passes validation.

{
  "object_name": "opportunities",
  "record_id": "uuid-opp",
  "data": {
    "budget": 50000,
    "decision_maker": "Alice Martin",
    "pipeline_step_id": "uuid-proposal-step"
  }
}

Examples

Create a contact linked to a company:

{
  "object_name": "contacts",
  "data": {
    "first_name": "Alice",
    "last_name": "Martin",
    "email": "alice@example.com",
    "entreprise_id": "7a2b3c4d-e5f6-7890-abcd-ef1234567890",
    "contact_status": "Lead"
  }
}

Create an opportunity (auto pipeline):

{
  "object_name": "opportunities",
  "data": {
    "name": "Acme Corp renewal",
    "amount": 50000,
    "close_date": "2025-06-30"
  }
}

The response includes the auto-assigned pipeline_id and pipeline_step_id.

Batch upsert (create two, update one):

{
  "object_name": "contacts",
  "data": [
    { "first_name": "Paul", "last_name": "Durand" },
    { "first_name": "Sophie", "last_name": "Bernard" },
    { "id": "550e8400-e29b-41d4-a716-446655440000", "contact_status": "customer" }
  ]
}

Create a contact and add it to lists in the same call:

{
  "object_name": "contacts",
  "data": { "first_name": "Paul", "last_name": "Durand" },
  "add_to_list_ids": ["a529e4c5-…"],
  "create_list_name": "Salon 2026"
}

Response

Single mode returns { id, action, record }:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "action": "created",
  "record": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Alice",
    "last_name": "Martin",
    "entreprise_id": "7a2b3c4d-e5f6-7890-abcd-ef1234567890",
    "entreprise_id__display": "Acme Corp",
    "contact_status": "Lead",
    "owner_id": "user-uuid",
    "pipeline_step_id": "first-step-uuid"
  }
}

action is "created" for new records and "updated" for updates. The record always contains the full enriched record with display values.

Array mode returns per-record results with counts:

{
  "object_name": "contacts",
  "total": 3,
  "created": 2,
  "updated": 1,
  "failed": 0,
  "results": [
    { "id": "d244…", "action": "created" },
    { "id": "fe69…", "action": "created" },
    { "id": "550e…", "action": "updated" }
  ]
}

Augmentation outcomes (lists, created_list, removed_from_lists, relations, updated_list_entries) are appended to the response when those params are used. A failed list addition never rolls back the record write — the failure is reported per list.

Error handling

Validation error (field-level details):

{
  "error": true,
  "code": "VALIDATION_ERROR",
  "message": "Validation failed",
  "field_errors": [
    { "field": "email", "message": "Must be a valid email address" }
  ]
}

Duplicate record (unique constraint violation):

{
  "error": true,
  "code": "DUPLICATE_RECORD",
  "message": "A contact with this email already exists"
}

crm_bulk_update_records

Apply one change across many records at once: set a single field to the same value, or add/remove a many-to-many link in bulk. For different values per record, use crm_save_records with a data array instead.

Destructive — it overwrites in bulk. Confirm the target record set with the user first. Max 200 records per call.

Parameters

ParameterTypeRequiredDescription
actionupdate_field | add_relation | remove_relationYesThe bulk operation.
object_namestringYesTarget object.
record_idsstring[]YesUUIDs of the records to update (max 200).
field_namestringNoField to set (update_field only).
field_valueanyNoNew value for every record (update_field only).
relation_fieldstringNoM2M relation field name (add_relation / remove_relation).
target_idsstring[]NoTarget record UUIDs to link/unlink (add_relation / remove_relation).

Example

Tag 3 contacts as customers:

{
  "action": "update_field",
  "object_name": "contacts",
  "record_ids": ["550e…", "661f…", "772a…"],
  "field_name": "contact_status",
  "field_value": "customer"
}

crm_delete_records

Delete one or more records — soft-delete via the canonical path (RBAC + dashboard-owner guard + cascade of the records' list entries). This is the only way to remove records; crm_save_records cannot delete.

Irreversible and easy to get wrong. The assistant should re-read the records, show them to you, and delete only after explicit confirmation. Max 50 per call.

Parameters

ParameterTypeRequiredDescription
object_namestringYesCRM object name.
record_idsstring[]YesUUIDs to delete (max 50).

Example

{
  "object_name": "contacts",
  "record_ids": ["550e8400-e29b-41d4-a716-446655440000"]
}

crm_move_deal

Move a deal to a different pipeline step.

Parameters

ParameterTypeRequiredDescription
record_idstringYesUUID of the deal to move.
step_idstringYesUUID of the target pipeline step (from kasar://pipelines).
object_namestringNoObject with a PIPELINE field. Default opportunities.
pipeline_idstringNoUUID of the target pipeline — only needed to move the deal into a step in a different pipeline than its current one.

Behavior

Always read kasar://pipelines first — it lists every step with its questions, required_for_next_stage, required_for_closure, and forced_value flags.

  • Before moving, set the required fields of the source step (and target, for closures) on the record via crm_save_records. Otherwise the server returns MISSING_REQUIRED_FIELDS and the move is rejected.
  • The server applies the forced/default values of the target step automatically and records a witness row in pipeline_step_history.
  • The caller must have edit permission on the record.

Example

{
  "object_name": "opportunities",
  "record_id": "uuid-opp",
  "step_id": "uuid-proposal-step"
}

Response

{
  "deal_id": "uuid-opp",
  "pipeline_step_id": "uuid-proposal-step",
  "action": "moved"
}

If required fields are missing, the response is a MISSING_REQUIRED_FIELDS error listing the missing fields in field_errors.


crm_manage_duplicates

Detect duplicate records on an object and optionally merge them.

Both actions are admin-only. Renamed from crm_find_duplicates.

Parameters

ParameterTypeRequiredDescription
actiondetect | mergeYesdetect finds duplicate groups; merge consolidates a group into a master.
object_namestringNoObject to check: contacts or companies (default contacts).
limitnumberNoMax duplicate groups to return (detect only, default 20).
fieldsstring[]NoFields to exact-match on (detect only). Default: email, phone, linkedin, name.
master_idstringNoUUID of the record to keep (required for merge).
duplicate_idsstring[]NoUUIDs of records to merge into the master (required for merge).
merge_strategykeep_master | fill_blanksNokeep_master: only delete the duplicates. fill_blanks: copy non-empty fields from duplicates into the master's empty fields before deleting (default fill_blanks).

detect groups records that exactly match (case-insensitive, trimmed) on one of the compared fields and returns each group with the matched-field reason. merge runs the same engine as the app's Duplicates tab — relations, junctions and compound values are transferred to the master, then the duplicates are deleted.

Examples

Detect duplicate contacts by email and phone:

{
  "action": "detect",
  "object_name": "contacts",
  "fields": ["email", "phone"],
  "limit": 50
}

Response:

{
  "object_name": "contacts",
  "duplicate_groups": [
    {
      "records": [
        { "id": "550e…", "full_name": "Alice Martin", "email": "alice@example.com" },
        { "id": "661f…", "full_name": "Alice M.", "email": "alice@example.com" }
      ],
      "match_reason": "email: \"alice@example.com\""
    }
  ],
  "total_groups": 1,
  "records_scanned": 1240
}

Merge two contacts into a master:

{
  "action": "merge",
  "object_name": "contacts",
  "master_id": "550e8400-e29b-41d4-a716-446655440000",
  "duplicate_ids": ["661f9511-f30c-52e5-b827-557766551111"],
  "merge_strategy": "fill_blanks"
}

Response:

{
  "master_id": "550e8400-e29b-41d4-a716-446655440000",
  "duplicates_deleted": 1,
  "updated_relations": 7,
  "strategy": "fill_blanks"
}

crm_manage_emails_phones

Manage a record's emails or phone numbers. These are child records in a separate table — not a scalar field — so they are edited here, never through crm_save_records.

Parameters

ParameterTypeRequiredDescription
object_namestringYesParent object (e.g. contacts).
record_idstringYesParent record UUID.
field_typeemails | phonesYesWhich compound field to manage.
actionlist | add | remove | set_primaryYesThe operation.
valuestringCond.The email address or phone number (add). The first entry added becomes primary automatically.
child_idstringCond.The entry id (from list) — required for remove and set_primary.
company_idstringNoOptional company to link the entry to (add).

Example

"Add a second email to this contact"

{
  "object_name": "contacts",
  "record_id": "550e8400-e29b-41d4-a716-446655440000",
  "field_type": "emails",
  "action": "add",
  "value": "alice.pro@example.com"
}

Tips

  • crm_find_records and crm_save_records also work on the tasks and notes objects, since they support any CRM object.
  • For tasks-specific features (completion toggling, linked entities) use the dedicated crm_manage_tasks tool. For notes-specific features (visibility controls, @mention notifications) use crm_manage_notes.
  • To narrow columns or export a slice, use crm_export_records. To bulk-create from an analyzed dataset, use crm_import_records.

On this page