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

# Tools Reference

> Input schema, behavior, and example calls for all four MCP tools: search_apis, describe_api, invoke_api, and list_custom_fields.

Every tool call is a standard MCP `tools/call` request and returns a standard `CallToolResult`: a `content` array (a text block your model reads) plus, on success, `structuredContent` (the same payload as parsed JSON, for clients that support it). A failed call sets `isError: true` and puts a message the model can act on in the text block — never a raw stack trace or protocol error.

## search\_apis

Find which operations can serve an intent. Call this whenever you don't already know the exact `operationId` — it searches every available operation and returns candidates with their id and a one-line summary.

<ParamField body="query" type="string" required>
  What you're trying to do, in plain words — e.g. "find buyer leads created last week" or "reassign a lead to another agent".
</ParamField>

<ParamField body="limit" type="integer" default="8">
  Maximum results to return. Maximum `25`.
</ParamField>

```bash Request theme={null}
curl https://mcp.lofty.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer <TOKEN>" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "tools/call",
    "params": {"name": "search_apis", "arguments": {"query": "get a lead by id"}}
  }'
```

```json Response theme={null}
{
  "operations": [
    {"operationId": "lead_get", "method": "GET", "path": "/v1.0/leads/{leadId}", "summary": "Get Lead"},
    {"operationId": "leadTask_getTaskById", "method": "GET", "path": "/v1.0/tasks/{taskId}", "summary": "Get Task (V1)"}
  ],
  "next": "Call describe_api with an operationId to see its parameters."
}
```

<Tip>
  Ranking is a weighted keyword match over each operation's real documentation — not semantic search. The right operation reliably appears within the top few results, but isn't always first. Read the summaries of results 2–5 before concluding nothing fits or retrying with different wording.
</Tip>

## describe\_api

Return the full parameter schema for one operation: every argument `invoke_api` accepts, with types, allowed values, and documentation. Call this after `search_apis` and before `invoke_api`.

<ParamField body="operationId" type="string" required>
  An `operationId` returned by `search_apis`, e.g. `lead_update`.
</ParamField>

```bash Request theme={null}
curl https://mcp.lofty.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer <TOKEN>" \
  -d '{
    "jsonrpc": "2.0", "id": 2, "method": "tools/call",
    "params": {"name": "describe_api", "arguments": {"operationId": "lead_get"}}
  }'
```

```json Response theme={null}
{
  "operationId": "lead_get",
  "method": "GET",
  "path": "/v1.0/leads/{leadId}",
  "summary": "Get Lead",
  "description": "Returns a single lead by its ID...",
  "inputSchema": {
    "type": "object",
    "properties": {
      "leadId": {"type": "integer", "description": "ID of the lead to retrieve."},
      "withTrash": {"type": "boolean", "description": "When true, include trashed leads in the lookup."}
    },
    "required": ["leadId"]
  },
  "readOnly": true
}
```

If the operation modifies or deletes data, the response includes a `"warning"` field saying so — confirm with the user before calling it.

## invoke\_api

Execute one operation. Path and query parameters go at the top level of `arguments`; a request body goes under a nested `body` key.

<ParamField body="operationId" type="string" required>
  The operation to call, as returned by `search_apis`.
</ParamField>

<ParamField body="arguments" type="object">
  Arguments for the operation, exactly as described by `describe_api`.
</ParamField>

```bash Request (path parameter) theme={null}
curl https://mcp.lofty.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer <TOKEN>" \
  -d '{
    "jsonrpc": "2.0", "id": 3, "method": "tools/call",
    "params": {"name": "invoke_api", "arguments": {"operationId": "lead_get", "arguments": {"leadId": 100001}}}
  }'
```

```bash Request (with a body) theme={null}
curl https://mcp.lofty.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer <TOKEN>" \
  -d '{
    "jsonrpc": "2.0", "id": 4, "method": "tools/call",
    "params": {
      "name": "invoke_api",
      "arguments": {
        "operationId": "lead_update",
        "arguments": {"leadId": 100001, "body": {"firstName": "Ada"}}
      }
    }
  }'
```

A successful call's text content is the underlying API's own JSON response body, unchanged. A failed call — invalid arguments, an operation the caller isn't authorized for, a record that doesn't exist — is returned as a tool error carrying the real HTTP status and body:

```json Error example theme={null}
{
  "content": [{"type": "text", "text": "The Lofty API returned HTTP 404: {\"message\":\"...Lead not exist\"}"}],
  "isError": true
}
```

<Warning>
  `invoke_api` runs as the authenticated caller with their existing permissions — a call the user couldn't make through the CRM directly will be refused here too. Operations `describe_api` marks as modifying or deleting data should be confirmed with the user before calling.
</Warning>

<Note>
  A very large response is truncated with a leading `NOTE: ...` line explaining what was cut and how to get the rest — read that line before treating a trimmed list as the complete answer.
</Note>

## list\_custom\_fields

Return the custom lead field definitions configured on the caller's team, with the ids to use when reading or writing their values. Custom fields are defined per team, so they can't appear in any operation's static schema — call this before setting or filtering on a field name you don't recognize.

This tool takes no arguments.

```bash Request theme={null}
curl https://mcp.lofty.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer <TOKEN>" \
  -d '{
    "jsonrpc": "2.0", "id": 5, "method": "tools/call",
    "params": {"name": "list_custom_fields", "arguments": {}}
  }'
```

```json Response theme={null}
[
  {"attributeName": "custom text", "attributeType": "text", "value": "", "params": "{\"attributeName\":\"custom text\",\"attributeType\":\"text\"}"},
  {"attributeName": "custom signSelect", "attributeType": "single_select", "value": "", "params": "{\"attributeName\":\"custom signSelect\",\"attributeType\":\"single_select\",\"option\":[\"A1\",\"B1\",\"C1\"]}"}
]
```
