Custom scoped tools

Developer

Custom tools are purpose-named invocable actions scoped to one domain. The generic actions cover everything; a scoped tool covers one thing, so an agent can pick a tool by its purpose. A tool called Sales Desk Context, with a purpose statement, tells the agent which records it answers over. Generate them on Deploy → Custom.

How it works

@InvocableMethod(label='Sales Desk Context' category='ContextWorks'
    description='Everything a seller needs about an account and its open business. Pass any covered record Id; uncovered objects return the current coverage list.')
public static List<Response> execute(List<Request> requests) {
    // ...
    ctxl.EntityRequest req = new ctxl.EntityRequest();
    req.domainApiName = 'sales_desk';
    req.toolName      = 'SalesDeskGetRecordsAction';
    req.recordId      = request.recordId;
    req.level         = 'detailed';
    req.format        = 'markdown';
    response.result   = ctxl.Entity.load(req);
    // ...
}
  • The class names a domain, not an entity list. It reads the domain record at call time, so adding an object to the domain widens what the tool covers with no code change.
  • toolName stamps telemetry. It becomes the Surface value in Usage, so this tool’s loads read on their own.
  • The description states a purpose, so it stays true when the domain’s contents change.

The code is yours to edit. Sharpen the @InvocableMethod description, add inputs, post-process the payload. Keep toolName, or Usage stops attributing the loads, and keep domainApiName, or the tool stops following the domain.

Generate the classes

Deploy → Custom

SettingWhat it does
DomainWhich domain’s roster the tools read through
LevelPin a level, or expose it as a caller input
FormatPin markdown, JSON, or both (the JSON payload with the markdown rendering inside it) — or expose it as an input

You get six classes, each with a Copy button:

ClassRole
<Domain>GetCatalogActionDiscovery — what this domain serves right now
<Domain>FindRecordsActionFiltered sets from the domain
<Domain>GetRecordsActionOne record in depth, entity chosen by the domain
<Domain>GetBriefsActionThe standing frame the domain serves — injected briefs first, in order
<Domain>GetSkillActionA procedure the domain carries, fetched by name
<Domain>UpdateRecordsActionUpdates the domain permits, with its confirmation posture

The write tool is where pinning the domain matters most. The domain decides whether it permits writes at all, which fields an agent may change, and whether a person confirms first. An agent handed the pinned class cannot reach past any of it. → Write access

Pin level or format when the tool has one job. Fewer inputs is less for an agent to get wrong. Expose them when the same tool serves several depths; the generated class adds the input with an agent-readable description.

Deploy the classes

The classes live in your org and namespace. Deploy them as you deploy any Apex. They call only the package’s public API, Entity, EntityRequest and EntityFindRequest, so package upgrades and changes to what a domain carries don’t break them.

Register them

Setup → Agent Actions → New → Apex, once per class, or add them as MCP tools on a server. Name each action exactly as the class — the same rule as the built-in actions. Step 3A on the Deploy page shows the agent action table; 3B covers MCP.

To add them to an existing agent, put the domain’s actions in a topic of their own, then add a transition from your agent’s router. One topic per domain keeps the agent’s choices clear. Starting fresh? The Agentforce page generates a whole agent. Swap its apex:// targets for your class names.

Leave the update tool out of the topic if that agent should not write. The domain still has to allow the write, but a tool the agent never sees is the clearer statement.

Error handling

Pass a record whose object the domain doesn’t serve and the tool returns a structured answer naming what the domain does cover, rather than throwing, so the agent can re-plan. The catalog tool gives the same answer up front. Agents should call it first.

Custom source or custom tool?

You wantBuild
A new kind of context — computed, external, scoredAn enrichment
A new way to address existing contextA scoped tool

What’s next