> ## Documentation Index
> Fetch the complete documentation index at: https://docs.morphic.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools Guide

> Conventions that apply across every Morphic MCP tool — result shapes, errors, slugs, bulk calls.

The [Tools Reference](/mcp/tools-reference) is generated from the live server and lists every tool's parameters. This page is the hand-written companion: the conventions the reference doesn't repeat 103 times.

## Result shape

Every tool returns the standard MCP content envelope with a single text part containing pretty-printed JSON:

```json theme={null}
{ "content": [{ "type": "text", "text": "{ … }" }] }
```

A few tools return a bare string rather than JSON — `ping` is one. The memory tools (`morphic_recall`, `morphic_attachment_recall`, `morphic_remember`) return **JSON** on success; older integrations that parsed their bare-string output need updating.

## Error envelope

<Note>
  Every tool converges on one machine-readable error shape. Branch on `error_code`; if your agent simply relays `user_message` verbatim, the wording is already correct and product-voiced.
</Note>

Failures return `isError: true` and a JSON body:

```json theme={null}
{
  "success": false,
  "error_code": "not_found",
  "user_message": "I couldn't find that record.",
  "retryable": false
}
```

| Field          | Meaning                                                                                                                                                 |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `success`      | Always `false` on this envelope                                                                                                                         |
| `error_code`   | Stable machine code — see below                                                                                                                         |
| `user_message` | Stable, product-voice wording. Safe to show an end user as-is                                                                                           |
| `retryable`    | `true` when the same call may succeed if retried. Retries should never be narrated to the user                                                          |
| `detail`       | Present only for validation failures (400/422), scrubbed. This is the one case where backend detail is actionable — e.g. "status slug `X` is not valid" |

### Error codes

| `error_code`                   | Retryable | Typical cause                                                                                                                               |
| ------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_input`                | No        | Schema or value the backend rejected                                                                                                        |
| `not_found`                    | No        | The id doesn't exist, or isn't visible to this key                                                                                          |
| `not_connected`                | No        | The connection expired, was revoked, or was never established — reconnect. Distinct from `permission_denied`: no permission change fixes it |
| `permission_denied`            | No        | Authenticated, but not allowed to reach that workspace data                                                                                 |
| `workspace_selection_required` | No        | The grant covers several workspaces and none was named — retry with the `workspace` argument                                                |
| `conflict`                     | Yes       | Record changed underneath you — re-read and retry                                                                                           |
| `insufficient_credits`         | No        | Workspace is out of credits for that feature                                                                                                |
| `rate_limited`                 | Yes       | Too many requests                                                                                                                           |
| `service_unavailable`          | Yes       | Backend didn't respond successfully                                                                                                         |
| `timeout`                      | Yes       | Backend took too long                                                                                                                       |
| `addon_not_installed`          | No        | Paid add-on isn't installed for this workspace                                                                                              |
| `addon_tool_failed`            | Yes       | Add-on provider errored                                                                                                                     |
| `research_failed`              | Yes       | An LLM-backed research/analysis tool couldn't complete                                                                                      |
| `provider_not_configured`      | No        | That capability isn't configured for this workspace                                                                                         |

Backend prose never reaches the model: `HTTP {status}: {detail}` strings, component names, tenant ids and stack traces are sanitised at a single boundary. Full diagnostics still go to the server's stderr for operators.

<Warning>
  Wiki tools previously returned failures **without** `isError`, which made a failed write look like a success. They now use the envelope above. If you special-cased wiki responses, remove that code.
</Warning>

## Choosing a workspace per call

Every tool takes an optional **`workspace`** argument: a workspace slug, on any call.

It only matters when a connection was approved for more than one workspace. Then the server cannot guess which one a request means, and answers `workspace_selection_required` until one is named. Set `workspace` and the call proceeds:

```json theme={null}
{ "name": "list_entities", "arguments": { "object_type_slug": "deals", "workspace": "acme" } }
```

Call `list_workspaces` for the slugs this connection may use. Omit the argument to use whatever the connection already resolves to — a sole approved workspace, or the `?workspace=` on the connect URL.

<Note>
  The argument is a request, not a decision. The server re-checks it against what you approved on the consent screen **and** your live membership on every call, so a workspace outside the grant is refused no matter what a client sends.
</Note>

## Object-type slugs are plural

Morphic's default object types are **`people`**, **`companies`**, **`deals`** — not `person`/`company`/`deal`. Anywhere a tool takes `object_type_slug` or `entity_type`, use the plural form.

Workspaces can define custom object types, so never hard-code the list. Call `list_entity_types` for the real slugs, then `get_entity_type_schema` for a type's field slugs before writing to it.

## Task statuses are workspace-configured

`create_activity.status` and `update_activity.status` take a **workspace task-status slug**, not a fixed enum. Call `list_task_statuses` to get this workspace's exact slugs.

<Warning>
  There is no `pending` status. Older documentation listed it; it was never valid. Common real slugs are `not_started`, `in_progress`, `completed`, `cancelled` — but confirm with `list_task_statuses`.
</Warning>

`activity_type` and `priority` *are* fixed enums, and are enforced at the schema level:

* `activity_type`: `task` · `email` · `call` · `meeting` · `note` · `stage_change`
* `priority`: `low` · `medium` · `high`

## `create_activity` vs `create_note`

Both attach text to a record; they land in different places.

* `create_activity(activity_type="note", …)` → a **timeline** note, ordered chronologically alongside calls, emails and meetings.
* `create_note(…)` → a standalone, editable, pinnable note in the record's **notes panel**.

Note bodies are stored exactly as sent. Pass real newlines — the server no longer rewrites literal `\n` sequences into line breaks, so an escaped string arrives escaped.

## Bulk tools

Every `bulk_*` tool takes an array of up to **200** ids and applies the *same* change to all of them. For per-record different values, loop the single-record tool instead.

Bulk calls return **per-id success/failure**, so one bad id does not abort the batch. Always inspect the per-id results rather than assuming a 200 means everything applied.

Bulk tools available: `bulk_update_entities`, `bulk_archive_entities`, `bulk_delete_entities`, `bulk_link_entities`, `bulk_unlink_entities`, `bulk_create_activities`, `bulk_update_activities`, `bulk_archive_activities`, `bulk_delete_activities`, `bulk_create_notes`, `bulk_delete_notes`.

## Linking and unlinking records

`link_entities` and `unlink_entities` take the **same three arguments** — the relation is addressed by attribute slug, not by a relation id:

```json theme={null}
{
  "entity_id": "<source uuid>",
  "attribute_slug": "company",
  "target_entity_id": "<target uuid>"
}
```

<Warning>
  Older documentation said `unlink_entities` takes `entity_id` + `relation_id`. It does not, and never has in the shipped server — that call fails schema validation. There is no need to save a `relation_id` from `link_entities`.
</Warning>

The attribute slug is resolved to its internal UUID for you. Resolution costs two extra backend calls and the cache is per-request, so a tight loop of links is noticeably slower than `bulk_link_entities`.

## Answering a question: start with Context

For any question about this workspace — a named record of any object type, **or** what people have said or done (Slack, email, meetings, chat, "did anyone mention X") — call `ask_context` **before** any search tool. Pass the user's question verbatim; do not pre-extract names or look up an id first. You do have conversation history; it reaches you through this tool, not through Slack's or Gmail's own APIs. Never tell the user you cannot search past messages. If `contexts` is empty, `semantic` is still a complete answer — Slack-only workspaces do not auto-create CRM records. Empty `semantic` too means nothing is indexed yet.

`ask_context` returns these keys:

| Key             | What it is                                                                                                                                                     |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resolved`      | Which records the question turned out to be about. May be empty.                                                                                               |
| `primary_layer` | `gold`, `semantic`, or `empty` — which layer to treat as the answer.                                                                                           |
| `contexts`      | Per record, a compacted brief of its current state, plus a relationship layer. **Answer from here when a record resolved.**                                    |
| `semantic`      | Recall over the workspace's indexed history, scoped to the question. Populated even when `resolved` is empty. On Slack-only workspaces this **is** the answer. |
| `next_steps`    | Which deeper tool to call, and when, if the layers above fall short. When Gold is empty, includes `narrower_question`: the slice is not the full thread.       |

It never errors on a question it cannot resolve — an unmatched question still returns `semantic` and `next_steps`.

Only then reach for the deeper layers, and only for the reason `next_steps` gives:

* `get_entity` / `get_entity_with_relations` — you need a record's raw field values.
* `list_activities` — you need the chronological timeline itself.
* `search_entities` / `list_entities` — `resolved` was empty or wrong, or you want a ranked list rather than one record's story.
* `morphic_recall` — the question is about durable cross-session memory, not a record.

If you already hold a record id, `get_context` is the entity-scoped form of the same call.

The same layers are available over REST: `POST /api/gold/context/ask` (body: `question`, optional `max_entities`, `include_graph`) and `GET /api/gold/{entity_id}/context`.

## Finding records

`search_entities` is semantic (vector) search. When nothing matches it returns an explicitly empty result rather than an error:

```json theme={null}
{
  "items": [],
  "total": 0,
  "hint": "no_match",
  "note": "No CRM matches for this query. Try list_entities with a keyword or exact field filter, or ask the user for a name or email to narrow it down."
}
```

`list_entities` is the deterministic counterpart and does far more than paginate. Beyond `object_type_slug`, `search`, `is_archived`, `page` and `page_size` it accepts:

* **`entity_ids`** — hydrate a specific set of records in one call.
* **`sort_by`** — a sort expression, e.g. `created_at:desc` or `deal_value:asc`.
* **`filters`** — field-level conditions keyed by attribute slug, e.g.
  ```json theme={null}
  {
    "deal_value": { "operator": "greater_than", "value": 10000 },
    "stage":      { "operator": "is", "value": "negotiation" }
  }
  ```
  The 19 operators are `is`, `equals`, `is_not`, `not_equals`, `contains`, `not_contains`, `starts_with`, `ends_with`, `greater_than`, `less_than`, `greater_than_or_equal`, `less_than_or_equal`, `after`, `before`, `between`, `is_empty`, `empty`, `not_empty`, `is_not_empty`. Omit `value` for the emptiness operators; pass `value2` with `between`.

Call `get_entity_type_schema` first so your slugs and operators match the attribute types.

## Knowledge base list results are lossy

`list_kb_documents` deliberately shrinks its payload: each item's `content_markdown` is truncated to a \~160-character excerpt and the BlockNote `content` field is removed entirely. Use it to discover what exists; call `get_kb_document` for a real body.

`fetch_all=true` flattens every folder level. `page` / `page_size` are still sent alongside it and their interaction with `fetch_all` is backend-defined — prefer one or the other, not both.

## Automations

`author_workflow` is the only way to build automation logic. No tool exposes raw React Flow graph JSON: authoring is delegated to the same LLM-backed builder the Morphic UI uses, from a natural-language description.

Two current engine limits to plan around:

* **Linear only.** Condition and branch nodes are not evaluated by the execution engine yet.
* **No retries.** A failed step aborts the entire run.

Typical sequence: `create_workflow` (draft metadata) → `author_workflow` (trigger + steps) → `publish_workflow` (go live) → `enqueue_workflow_manual_run` (run it now, for real) → `list_workflow_runs` / `get_workflow_run` (inspect).

## Optional request headers

| Header                                           | Purpose                                                                                                                                                                                              |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Idempotency-Key`                              | Opaque token forwarded unchanged to the backend for that single tool call. Scoped per request; the MCP server never inspects it                                                                      |
| `X-Backend-URL`                                  | Route this request's tool calls at a different Morphic backend. The origin must be allowlisted server-side or the request is rejected with 400. Auth validation always uses the server's own backend |
| `X-Morphic-Channel`, `X-Morphic-Actor-Assertion` | Agent-transport metadata forwarded to the backend, which is the only component that verifies the assertion                                                                                           |

## Where to go next

* [Tools Reference](/mcp/tools-reference) — every tool, generated from the server
* [Tool Groups](/mcp/tool-groups) — advertise only the tools you need
* [Common Workflows](/mcp/workflows) — end-to-end worked examples
* [Troubleshooting](/mcp/troubleshooting)
