building-tools
Build Celigo tool resources -- reusable building blocks that encapsulate lookups, imports, transforms, and branching behind input/output contracts. Callable from flows, APIs, AI agents, MCP servers, and other tools. Use when creating tools, adding steps, or configuring routing.
By celigo · 1,044 installs
npx skills add celigo/ai --skill building-tools
Source repository · Upstream listing
<! TIER:1
Building Tools
A tool is Celigo's first class reusable building block . It encapsulates logic lookups, imports, transforms, branching behind a defined input and output contract. Build it once, use it everywhere: from Flows, APIs, AI Agents, MCP Servers, and other Tools.
Tool Concepts
Why tools exist: Without tools, users build the same lookup transform import patterns repeatedly across Flows, APIs, and Agents. Tools solve this by providing a governed, composable abstraction: one definition, many consumers, consistent behavior.
When to build a tool:
You're building an MCP server MCP servers expose tools as endpoints; every piece of logic an MCP server offers must be a tool
The same logic is needed by 2+ consumers (flows, APIs, agents, MCP servers) build once, call everywhere
You want connection flexibility callers can pass different connections to the same tool definition
You're composing smaller pieces tools can call other tools for nested orchestration
When NOT to build a tool:
The logic is specific to one flow or API and won't be reused inline it as a lookup/import processor directly
You need abstract/instance templating for multi tenant patterns use abstract flows
Tool vs API: the split is the contract callers get. A tool is called by flow steps, AI agents, APIs and other tools, and outside callers can reach it too: a caller authenticated to the Celigo REST API can run it directly through its invoke operation (input plus connection bindings; the output comes back synchronously), and an MCP server publishes tools to outside AI clients. Put an API in front only when callers need their own URL, method and status codes (a partner system, a customer facing app) the tool stays available to its other consumers at the same time.
Tool vs flow: tools don't start themselves no schedule , no listeners, no flow level runtime controls, and no abstract/instance layer (a tool is already the unit of reuse; per environment variation is handled by the consumer bound connection model). A step's own proceedOnFailure is set on its page processor entry, as in a flow ([step 7]( 7 wire page processors into branches)). "Every night, do X" or "when a webhook arrives, do Y" is a flow the tool may be the thing the flow does .
Architecture:
Routers hold branches, branches hold page processors. Use multiple branches when different inputs need different processing paths. Chain routers via nextRouterId for sequential processing stages. The special nextRouterId: "outputRouter" exits the tool and returns results.
Tool Execution Pipeline
When a tool is invoked:
1. Input received input data validated against the tool's JSON Schema ( input )
2. Router evaluation routeRecordsUsing evaluates branch conditions (if multiple branches exist)
3. Branch selection matching branch processes the input
4. Page processors each processor executes sequentially (export lookups, import writes)
5. Response mapping responseMapping on each processor carries data to the next processor
6. Output mapping final output mapped according to the tool's output configuration (schema, mappings, lookups, hooks)
7. Return output returned to the caller (flow, API, AI agent, MCP server)
Execution mode depends on the caller:
Called from a Flow runs in flow mode (batch oriented, run console, error management)
Called from an API runs in API mode (single request/response)
Called from an AI Agent runs with agent context; agent maps connections to the tool
Called from an MCP Server exposed as an MCP tool endpoint for external AI clients
Called from another Tool nested execution within the parent tool's context
In a flow, an API or another tool, the tool runs as a ToolImport step: an import with "adaptorType": "ToolImport" and "tool": {" toolId": "<tool id ", "overrides": {"connections": [...]}} and no connectionId , which the parent's page processor references like any import ( {"type": "import", " importId": "<wrapper id "} ). Change the tool through tools and the wrapper through imports .
Connection model: Always bring your own keys. The caller (flow, API, agent, MCP server) maps connections to the tool at configuration time. MCP Server overrides can swap connections per server without modifying the tool.
Build order: Connection Export + Import Tool (consumer: Flow / API / Agent / MCP Server)
Concerns beyond the build steps:
Response mapping extract fields from processor responses back into the record. Configured on pageProcessors[] entries within branches as {"fields": [{"extract": ..., "generate": ...}], "lists": []} , but planned when building the processors. For lookups the response has data[] and errors[] (use data[0].fieldName for one field of the first result, or data[0] for the whole record), and data holds the records after the export's own transform and output filter, so use the transformed field names; for imports use id for the created record's ID and json.fieldName for response body fields (the body's fields are also at the root, so orderNumber reads the same as json.orderNumber ). An extract can be Handlebars over this envelope ( {{data.0.name}} ), which has no record : {{record.x}} renders nothing
postResponseMap hook JavaScript processing after response mapping. Also on pageProcessors[] entries, but planned when building the processors
The Two Flavors of Lookup
"Lookup" refers to two different things in a tool. They live in different places and solve different problems:
Branch page processor lookups ( pageProcessors[].type: "export" ) work doing lookups inside a router branch that call an external system at runtime ("look up the customer in NetSuite by email"). The data isn't in the tool yet; the export fetches it, and a responseMapping pulls fields from the response onto the record so downstream branches and the output can see them. This is the same lookup primitive flows use same export resource, same response mapping, same postResponseMap hook.
Output static lookup tables ( output.lookups[] ) declarative value translation tables on the output stage. They reach no external system; they are fixed key/value maps defined inline (e.g. a map translating A to Active , I to Inactive , P to Pending , with a default ). An output mappings[] entry references a table by its name (via lookupName ) to translate a field value during output assembly.
Rule of thumb: external system, runtime call branch page processor lookup. Translate one value into another via a fixed table output static lookup.
Consumer Bound Connections
A tool never pins connections at design time the key difference from flows and APIs:
A flow or API pins each of its own steps to a specific connection at build time the connection IDs are baked into the resource.
A tool's lookups and imports declare the connections they need , and the consumer supplies the actual connection at bind time when the tool is added as a flow step, attached to an AI agent, exposed behind an API endpoint, or embedded in another tool.
The tool definition stays unchanged across every binding. The same tool can be bound with a sandbox NetSuite connection from one consumer and a production NetSuite connection from another, without forking. Mechanically, the tool's page processors reference underlying export and import resources that carry the connection; binding selects which connection records those resources use in that context. Because of this, knowing which connections a tool requires is part of its design those connections must already exist in the consumer's account before the tool can be bound and run there.
Binding isn't optional. A direct invoke and a tool step (a ToolImport ) must list every connection the tool's steps use as {" abstractId": <authored id , " id": <connection to run with } in the invoke body's overrides.connections , or in the import's tool.overrides.connections . The same id twice keeps the authored connection, and leaving one out fails with connection not overridden ("Missing connection overrides"). The connection to run with goes in id : a binding that names it connectionId , or gives only abstractId , is refused with 422 "Invalid override at overrides.connections[0]. Connection override must include at least one of: id, ...". celigo tools connections <id lists the connections to bind. An MCP server binds per step instead; see [building mcp servers How to Build an MCP Server](../building mcp servers/SKILL.md how to build an mcp server).
Quick Reference
Decision Matrix
You need to... Build a tool? Instead use
Reuse logic across 2+ flows/APIs/agents Yes
Expose logic via MCP server Yes
Allow callers to swap connections Yes
Nest orchestration (tool calls tool) Yes
One off logic for a single flow No Inline lookup/import in flow
Multi tenant templating No Abstract/instance flows
Minimum Required Fields
Every tool needs at minimum: name , integrationId (a create without it fails with 422 "Expected field: integrationId to be present."), an input.schema , and a router whose branch has a step. A tool without steps saves, but every invoke and test run is refused ([step 6]( 6 configure routing if needed)).
Which Schemas to Read
1. Always: [request.yml](references/schemas/request.yml) (base fields for create/update)
2. Input config: [input.yml](references/schemas/input.yml) (schema, transform, mockInput)
3. Output config: [output.yml](references/schemas/output.yml) (mappings, lookups, hooks)
4. Pipeline config: [router.yml](references/schemas/router.yml) (routing strategy, branches, page processors, response mapping)
5. Response shape: [response.yml](references/schemas/response.yml)
Schema Index
All schemas are in [references/schemas/](references/schemas/):
Base fields (create/update): [request.yml](references/schemas/request.yml)
Response shape: [response.yml](references/schemas/response.yml)
Input configuration: [input.yml](references/schemas/input.yml) schema, transform, mockInput
Output configuration: [output.yml](references/schemas/output.yml) mappings, lookups, hooks
Router and branch configuration: [router.yml](references/schemas/router.yml) routing strategy, branches, page processors, response mapping
Related Skills
[configuring exports Quick Reference](../configuring exports/SKILL.md quick reference) building lookup exports used as steps in the tool pipeline
[configuring imports Quick Reference](../configuring imports/SKILL.md quick reference) building imports used as action steps in the tool pipeline
[building flows How to Build a Flow](../building flows/SKILL.md how to build a flow) wiring tools into flow pipelines
[building apis Quick Reference](../building apis/SKILL.md quick reference) exposing tools via API endpoints
[writing scripts Quick Reference](../writing scripts/SKILL.md quick reference) script hooks on tool page processors
[writing handlebars Quick Reference](../writing handlebars/SKILL.md quick reference) dynamic expressions in request bodies, URIs, and field mappings
[configuring filters Quick Reference](../configuring filters/SKILL.md quick reference) input filters on tool steps and router branches
<! TIER:2
How to Build a Tool
1. Determine the tool's purpose
What should this tool do when called? Define the inputs it expects and the processing steps it needs.
Write its description (up to 5,120 characters) for the model that decides when to call it: what it does now, what it takes and what it returns. An MCP server serves it verbatim as the tool's description, with the Tool's name as the title and its schemas as the input and output, and an AI agent step reads the same