The Tool Editor

A Tool is a single capability an AI Employee can invoke at runtime — "create a ticket," "look up an order," "send a message." The Tool editor is where you define exactly what a Tool does: the inputs it accepts, how it reaches the external system, and the shape of the result it returns. Every Tool belongs to an integration and inherits that integration's authentication.

Open the editor from an integration's Tools section by adding a new Tool or selecting an existing one. New Tools always start as a draft and become callable only after you publish them. The editor is a two-pane layout: the editable Tool definition on the left, and a live Test panel on the right.

Who can create and edit Tools. A Tool's edit access follows its integration's: the owner workspace's admins and the integration's Integration admins can create, edit, and publish its Tools. Everyone else can use the published Tools but can't change them. See Who can edit in Custom Integrations for the full model.

Anatomy of a Tool

Every Tool has the same core definition regardless of how it executes:

FieldPurpose
NameThe human-readable display name.
DescriptionWhat the Tool does. The agent reads this to decide when to call the Tool, so write it for the model, not just for humans.
Tool typeWhich type of Tool it is — HTTP, GraphQL, or TypeScript — chosen by what the API needs.
Input schemaThe typed parameters the Tool accepts.
Output schemaA description of what the Tool returns.
Write ToolA flag marking the Tool as state-changing (a write), which affects how the agent treats it.
TagsOptional labels for organizing Tools.

Defining the input schema

The input schema describes the parameters the Tool accepts. Each field has a name, a display name, a type, a required flag, and an optional description. Supported field types are:

string, date, number, boolean, enum (with a list of allowed values), object, and array.

Object and array fields can nest, up to three levels deep. Internally the schema round-trips to standard JSON Schema. The schema is used both as documentation and to validate the caller's input at runtime.

  • Depends on — declare that an input is supplied at runtime by the output of another published Tool in the same integration. This is how you chain Tools — for example resolving an account ID with one Tool before passing it to another.

Referencing inputs, credentials, and settings

HTTP Tools (URL, headers, body) and GraphQL Tools (query, variables) use {{...}} template references. There are three namespaces; the editor's linter flags anything else before you can ship a broken Tool:

ReferenceResolves toExample
{{input.X}}A value from the Tool's input, supplied by the caller at runtime{{input.channel}}
{{credential.X}}A connection-level credential (the connection's step-2 Credentials){{credential.api_token}}
{{setting.X}}A template-level setting (the connection template's step-1 Settings), such as the base URL{{setting.base_url}}

Insert a reference from the {x} popover, which lists exactly the inputs, credentials, and settings available for this Tool. Nested paths like {{input.user.email}} work as long as the top-level name exists. (TypeScript Tools don't use {{...}} — they read inputs from the generated Inputs object and reach the integration through call_integration.)

Quoting matters in a body. Because the body is a string template, a quoted "{{input.x}}" inserts the value as a JSON string, while an unquoted {{input.x}} injects it raw — use unquoted for objects, arrays, numbers, and booleans.

Some fields can't be referenced: the ones that drive the authentication flow itself (authorization/token URLs, client ID and secret, scopes) are blocked from templates for security — the {x} popover lists only the fields you can use.

Choosing a Tool type

Pick the type by what the target API needs and how much work the Tool must do:

TypeUse it when
HTTPOne REST request does the job and the response is usable as-is.
GraphQLThe provider is a GraphQL API — one query or mutation with typed variables.
TypeScriptOne request isn't enough — you need to transform inputs, branch on a response, page through results, or call several endpoints in sequence.

Tool type: HTTP

An HTTP Tool makes a single declarative request to a REST endpoint — use it when one REST call does the job. You configure:

  • MethodGET, POST, PUT, PATCH, or DELETE.
  • URL path — the request path, shown after a fixed Base URL prefix (from the integration's setting); you supply the relative path, e.g. /v1/teams/{{input.team_id}}. There is no separate query-parameters field — write query strings directly into the path (e.g. /search?q={{input.q}}&limit={{input.limit}}).
  • Headers — request headers as key/value pairs. Don't add an auth header here — Ema injects authentication automatically from the connection.
  • Body — the request body (for POST/PUT/PATCH). Use Generate from inputs to scaffold a JSON body from your input schema.

At call time Ema resolves the templates, injects the connection's auth, runs the request, and returns the response — you never handle the token yourself.

For example, a "Create candidate" Tool takes simple, flat inputs:

InputTypeRequired
first_namestringRequired
last_namestringRequired
emailstringRequired
phonestringOptional

…and the body assembles them into the nested shape the API expects (the optional phone uses a ? so it's dropped when absent):

{
  "candidate": {
    "name": "{{input.first_name}} {{input.last_name}}",
    "contact": {
      "email": "{{input.email}}",
      "phone": "{{input.phone?}}"
    }
  }
}

Keep inputs flat — simple, individually-typed fields are easy for the agent to fill and validate. Ema assembles the complex, nested payload in the body template, so the agent never has to build nested JSON.

Tool type: GraphQL

A GraphQL Tool runs a single GraphQL query or mutation — use it when the provider is a GraphQL API. You configure:

  • URL path — the GraphQL endpoint, after the Base URL prefix.
  • Query — the GraphQL document (static). Use {{setting.X}} for template-level values; do not put {{input.X}} in the query text — pass caller values through variables instead.
  • Variables — a JSON object whose values carry {{input.X}} references, e.g. {"id": "{{input.id}}"}. For an optional variable (such as a pagination cursor), use the ? suffix so an omitted value doesn't error: {"after": "{{input.after?}}"}.
  • Headers — key/value pairs.

Example — "Get user" looks up a user by their ID and returns their name. It takes a single input, id (string, required):

  • Query: query($id: ID!) { user(id: $id) { id name } }
  • Variables: { "id": "{{input.id}}" } — passes the id input into the query's $id.

Ema sends the { query, variables } envelope and returns the data — the user's id and name. A GraphQL errors response is treated as a failure even under HTTP 200, so write your output schema against the data shape.

Tool type: TypeScript

A TypeScript Tool runs your code in a sandbox instead of making a single declarative request. Reach for it when one call isn't enough — transform inputs, branch on a response, page through results, or call several endpoints in sequence.

The editor shows a read-only prelude (an Inputs interface generated from your inputs, plus the helpers below), then you write a function main(inputs) — whatever you return from main becomes the Tool's output. The code is synchronous: no async/await.

Call the integration's endpoints with call_integration(integration, method, path, body?, headers?) — Ema runs the request (applying auth server-side) and returns { ok, status, body, … }. Reference the integration as integrations["<name>"] (the Reference name, defaulting to the integration's slug). Your script never sees raw credentials.

For example, a Tool with inputs team_id (required) and limit (optional) that lists a team's issues and returns a clean id/title projection:

function main(inputs) {
  let r;
  try {
    r = call_integration(
      integrations["self"],
      "GET",
      "/v1/teams/" + inputs.team_id + "/issues?limit=" + (inputs.limit ?? 20)
    );
  } catch (e) {
    // Connection/auth problems throw — rethrow so Ema can prompt the user to connect.
    if (/USER_CONNECTION_REQUIRED|status 40[134]/.test(e.message)) throw e;
    return { error: "setup_error", detail: e.message };
  }
  if (!r.ok) return { error: "request_failed", status: r.status };   // upstream API error
  const data = JSON.parse(r.body);
  return { issues: data.issues.map(i => ({ id: i.id, title: i.title })) };
}

This covers both failure modes: call_integration throws on connection/auth/config problems — the try/catch rethrows auth errors so Ema prompts the user to connect, and returns a friendly result for other setup errors — while an upstream API error comes back as r.ok === false.

The prelude is your allowed list — it declares everything the sandbox provides: the integrations binding, call_integration, log, and utility libraries (lodash _, uuid, Papa for CSV, base64). If it isn't in the prelude, it isn't available — there's no LLM call, for example; reasoning belongs in the agent that calls the Tool.

Ema statically validates the script when you save and runs it in a sandbox at runtime. You can switch a draft between Tool types; the editor warns before discarding type-specific content you've already entered.

The output schema

The output schema tells the agent what the Tool returns so it can use the result. At minimum, provide a one-sentence output description — this is required to publish. The schema is documentation surfaced to the agent; it is not validated at runtime. After running a test, use Save response as schema to auto-derive the field list from the real response — this replaces the current output schema, so confirm when prompted.

Test, validate, and publish

The right pane runs the Tool against a live connection without leaving the editor:

  1. Pick a connection template that has a live connection.
  2. Save your changes — the test runs against the last saved version.
  3. Fill in a JSON input matching your schema and select Run test. The Output card shows the result, status, and duration.

Two more controls guard quality:

  • Validate checks the Tool's saved configuration for broken references and missing pieces — for example a {{credential.X}} that no longer exists.
  • Publish moves the Tool from draft to published, making it callable by AI Employees and Ema Autopilot. Publishing auto-runs Validate and requires a non-empty output description. Edits to a published Tool go live immediately, so make corrections deliberately.

The editor header also lets you Clone or Delete a Tool. Clone makes a fresh draft copy — use it to build a variant of an existing Tool, or to change a published Tool without affecting the live one (edit and publish the copy, then retire the original).

What's next

  • MCP Servers — add your own MCP server and use the tools it exposes.
  • Data Connectors — sync external documents into Knowledge bases so an AI Employee can cite them.

Last updated: Aug 27, 2026