---
title: "How do I create a custom tool?"
slug: "how-do-i-create-a-custom-tool"
updated: 2026-04-29T15:20:58Z
published: 2026-04-29T15:20:58Z
canonical: "support.lusid.com/how-do-i-create-a-custom-tool"
---

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

# How do I create a custom tool?

Custom tools extend Candela with capabilities specific to your organisation - no code required for your end-users.

A tool wraps either of the following and exposes it as a named tool that your Candela [agents](/v1/docs/understanding-candela-s-system-agents) can call:

- A [Luminesce SQL query](/v1/docs/understanding-the-luminesce-sql-query-syntax)
- A [Scheduler](/v1/docs/scheduler) job

For example, you could create a `GetPortfolioProperties` tool that returns all custom metadata attached to a portfolio, then assign it to a [specialist agent](/v1/docs/how-do-i-create-a-custom-agent) so your team can retrieve that data through a simple conversation prompt.

This article explains how to create and assign custom tools via the [LUSID Identity API](https://www.lusid.com/docs/api/identity/intro).

> [!WARNING]
> Prerequisites
> 
> - Suitable access permissions to create MCP tools in LUSID
> - For Luminesce tools: Your tested [Luminesce SQL query](/v1/docs/understanding-the-luminesce-sql-query-syntax)
> - For Scheduler tools: Your [Scheduler job](/v1/docs/how-do-i-create-a-job)’s scope and code

## Step 1: Create a tool

### Understanding the fields required to create a tool

All tools share the following fields:

| Field | Description |
| --- | --- |
| `name` | A tool name that’s unique within your domain. |
| `title` | A friendly title to show in tool listings. |
| `description` | A specific, action-oriented description for the AI to use to decide when to call the tool. |
| `destructive` | Set to `true` to require user confirmation before executing. [Read more.](/v1/docs/monitoring-candela-ai-activity) |
| `readOnly` | Set to `false` to have an agent confirm with a user before calling the tool. |
| `parameters` | A list of parameters that the tool can pass into the underlying Luminesce query or scheduler job. [See parameters below.](/v1/docs/how-do-i-create-a-custom-tool#parameters) |
| [luminescePayload](/v1/docs/how-do-i-create-a-custom-tool#luminescepowered-tools) or [schedulerPayload](/v1/docs/how-do-i-create-a-custom-tool#schedulerpowered-tools) | The query or job this tool executes; [more information below](/v1/docs/how-do-i-create-a-custom-tool#luminescepowered-tools). |

> [!NOTE]
> Note
> 
> The `idempotent` and `openWorld` fields are currently reference-only. In future, they will be used to govern how tools are executed and what external services they can access.

#### `parameters`

Each parameter declared in the `parameters` array can be referenced in your [luminescePayload](/v1/docs/how-do-i-create-a-custom-tool#luminescepowered-tools) query or [schedulerPayload](/v1/docs/how-do-i-create-a-custom-tool#schedulerpowered-tools) arguments using triple curly brace `{{{&lt;parameter&gt;}}}` syntax. When called, Candela substitutes each placeholder with the value provided in the conversation.

Each parameter requires the following fields:

| Field | Description |
| --- | --- |
| `name` | The parameter name, which you can subsequently reference in the payload as `{{{&lt;parameter&gt;}}}`. |
| `dataType` | The expected data type. |
| `description` | A specific description of the parameter for the agent to use to determine what value to pass. For example, `"The LUSID scope of the portfolio, for example 'acme'"` is more effective than `"The portfolio scope"`. |

### Luminesce-powered tools

Imagine you want to create a tool that can run the following [Luminesce query](/v1/docs/understanding-the-luminesce-sql-query-syntax):

```sql
select [PropertyScope], [PropertyCode], [Value] 
from [Lusid.Portfolio.Property] 
where [PortfolioScope] = '{{{portfolioScope}}}' and [PortfolioCode] = '{{{portfolioCode}}}'
```

You can call the [CreateMcpTool API](https://www.lusid.com/docs/api/identity/endpoints/mcp-tools/CreateMcpTool), passing in the fields from the table above, plus the following `luminescePayload`:

```json
curl -X POST 'https://<your-domain>.lusid.com/identity/api/mcptools/Finbourne-Examples/GetPortfolioProperties'
  -H 'Accept: application/json' 
  -H 'Authorization: Bearer <your-API-access-token>'
  -H 'Content-Type: application/json-patch+json' 
  -d '{
    "name": "GetPortfolioProperties",
    "title": "Get custom properties for a portfolio",
    "description": "Returns all custom properties attached to a portfolio. Use when the user asks about portfolio metadata, tags, or custom attributes.",
    "destructive": false,
    "idempotent": true,
    "openWorld": false,
    "readOnly": true,
    "parameters": [
      { "name": "portfolioScope", "dataType": "String", "description": "The LUSID scope of the portfolio" },
      { "name": "portfolioCode", "dataType": "String", "description": "The LUSID code of the portfolio" }
    ],
    "luminescePayload": {
      "query": "select [PropertyScope], [PropertyCode], [Value] from [Lusid.Portfolio.Property] where [PortfolioScope] = '\''{{{portfolioScope}}}'\'' and [PortfolioCode] = '\''{{{portfolioCode}}}'\''"
    }
  }
'
```

### Scheduler-powered tools

To create a tool that can trigger a scheduled job, you can call the [CreateMcpTool API](https://www.lusid.com/docs/api/identity/endpoints/mcp-tools/CreateMcpTool), passing in the fields [from the table above](/v1/docs/how-do-i-create-a-custom-tool#understanding-the-fields-required-to-create-a-tool), plus the following `schedulerPayload`:

```json
curl -X POST 'https://<your-domain>.lusid.com/identity/api/mcptools/Finbourne-Examples/RunComplianceJob'
  -H 'Accept: application/json' 
  -H 'Authorization: Bearer <your-API-access-token>'
  -H 'Content-Type: application/json-patch+json' 
  -d '{
  "name": "RunComplianceJob",
  "title": "Run a compliance scheduler job",
  "description": "Triggers the nightly compliance job for a given set of rules.",
  "destructive": false,
  "idempotent": false,
  "readOnly": false,
  "parameters": [
    {
      "name": "ruleScope",
      "dataType": "String",
      "description": "The rules scope to run compliance on"
    },
        {
      "name": "runScope",
      "dataType": "String",
      "description": "The scope to run compliance for"
    }
  ],
  "schedulerPayload": {
    "jobScope": "Finbourne-Examples",
    "jobCode": "RunCompliance",
    "arguments": {
      "ruleScope": "{{{ruleScope}}}",
      "runScope": "{{{runScope}}}"
    }
  }
}'
```

## Step 2: Assign the tool to an agent

Agents only call tools that you explicitly list in their `toolNames` array. [See how to create or update an agent.](/v1/docs/how-do-i-create-a-custom-agent)
