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

# Tools

> Assistant-callable functions with JSON Schema parameters and structured error responses.

Tools are functions the Ekanos assistant (and, where the host allows it, the workflow engine) can call. Each tool declares a name, a description the model reads, an optional parameter schema, and an async `run`.

## Basic tool

```ts theme={null}
tools: [
  {
    name: 'get_current_weather',
    description: "Current conditions for the account's saved location.",
    parameters: {
      type: 'object',
      properties: {
        units: { type: 'string', enum: ['celsius', 'fahrenheit'] },
      },
      required: [],
      additionalProperties: false,
    },
    outputExample: { temperature: 12.4, units: 'celsius' },

    async run(ctx, args) {
      const saved = await ctx.storage.account.get('settings/location');
      if (!saved) {
        return { error: 'No location saved for this account.' };
      }

      const response = await ctx.fetch(
        `https://api.acme.example/v1/weather?lat=${saved.data.latitude}&lon=${saved.data.longitude}`,
      );

      if (!response.ok) {
        ctx.logger.error({ status: response.status }, 'Forecast fetch failed.');
        return { error: `Acme returned ${response.status}.` };
      }

      return response.json();
    },
  },
];
```

## Handler behavior

### Success

Return a plain JSON-serializable value:

```ts theme={null}
async run(ctx, args) {
  return { temperature: 12.4, units: 'celsius' };
}
```

### Expected failure (user-facing)

Return `{ error: '…' }`. The model reads this and explains it to the user:

```ts theme={null}
async run(ctx, args) {
  const apiKey = await ctx.secrets.get('api_key');
  if (!apiKey) {
    return { error: 'This integration is not configured.' };
  }
  // ...
}
```

### Genuine fault (throw)

Throw for a genuine fault rather than returning `{ error }`:

```ts theme={null}
async run(ctx, args) {
  const response = await ctx.fetch('https://api.example/data');
  if (!response.ok) {
    throw new Error(`API returned ${response.status}`);
  }
  return response.json();
}
```

<Note>
  `PublicActionError` from `@ekanos/sdk/integration` belongs to `actions`
  (widget-invoked handlers called through `useIntegrationAction()`), not tools.
  An action handler's throw is, by default, collapsed to a generic `"Action
      failed."` response under the `handler_error` code; throwing
  `PublicActionError` surfaces its message to the user verbatim instead.
</Note>

## Parameters

Parameters are a narrow slice of JSON Schema — only the wrapper is restricted, not the contents:

```ts theme={null}
// ✅ Valid
parameters: {
  type: 'object',
  properties: {
    id: { type: 'string' },
    nested: {
      type: 'object',
      properties: { count: { type: 'number' } },
    },
    items: { type: 'array', items: { type: 'string' } },
    status: { enum: ['active', 'inactive'] },
  },
  required: ['id'],
  additionalProperties: false,
}

// ❌ Invalid — top level restriction
parameters: {
  type: 'string',  // Root must be 'object'
}

parameters: {
  title: 'My Schema',  // No title at root level
  type: 'object',
  // ...
}
```

**Valid at root:** `type` (must be `'object'`), `properties`, `required`, `additionalProperties`

**Invalid at root:** `title`, `description`, `$schema`, `oneOf`, anything other than the four above

Inside `properties`, you are unconstrained — nested objects, arrays, enums, descriptions all pass.

## Tool naming

Tool names are `^[a-z][a-z0-9_]*$` — lowercase snake\_case:

* ✅ Valid: `list_payments`, `get_customer`, `refund_payment`
* ❌ Invalid: `ListPayments`, `list-payments`, `list payments`

The effective name the assistant sees is namespaced by your slug. `acme-payments` + `list_payments` becomes `acme_payments_list_payments` (hyphens → underscores).

If you declare `acme_payments_list_payments` as the raw name, the effective name stays the same (no double prefix). Collisions across two integrations in the same platform are detected and rejected.

## Output example

Small, honest examples of what the tool returns. Used by the model as a shape hint:

```ts theme={null}
outputExample: {
  temperature: 12.4,
  units: 'celsius',
  location: 'San Francisco, CA',
}
```

Keep it short. The model uses it to understand the shape, not to hallucinate realistic data.

## Common patterns

### Read data

```ts theme={null}
{
  name: 'list_recent_payments',
  description: 'Recent payment transactions for the connected merchant.',
  parameters: {
    type: 'object',
    properties: {
      limit: { type: 'number', description: 'Max results, 1-100' },
    },
    required: [],
    additionalProperties: false,
  },
  // outputExample must be an object (plain JSON), not a top-level array
  outputExample: {
    payments: [{ id: 'pay_123', amount: 100.0, status: 'completed' }],
  },
  async run(ctx, args) {
    const apiKey = await ctx.secrets.get('api_key');
    if (!apiKey) {
      return { error: 'Integration not configured.' };
    }

    const response = await ctx.fetch(
      `https://api.acme.example/v1/payments?limit=${args.limit ?? 10}`
    );

    if (!response.ok) {
      return { error: `API returned ${response.status}` };
    }

    return response.json();
  },
}
```

### Perform an action

```ts theme={null}
{
  name: 'refund_payment',
  description: 'Issue a refund for a payment.',
  parameters: {
    type: 'object',
    properties: {
      paymentId: { type: 'string' },
      reason: { type: 'string' },
    },
    required: ['paymentId'],
    additionalProperties: false,
  },
  outputExample: { refundId: 'ref_456', status: 'processed' },
  async run(ctx, args) {
    const apiKey = await ctx.secrets.get('api_key');
    if (!apiKey) {
      return { error: 'Integration not configured.' };
    }

    const response = await ctx.fetch(
      'https://api.acme.example/v1/refunds',
      {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          paymentId: args.paymentId,
          reason: args.reason,
        }),
      }
    );

    if (!response.ok) {
      ctx.logger.error({ status: response.status }, 'Refund request failed.');
      return { error: `Acme returned ${response.status}` };
    }

    return response.json();
  },
}
```

## Testing

Use `createMockContext()` to test tools:

```ts theme={null}
import { createMockContext } from '@ekanos/sdk/testing';

const ctx = createMockContext({
  integration: { slug: 'acme-payments' },
  storageSchemas: myStorage,
  egress: integration.egress,
  storage: {
    account: {
      'settings/location': {
        label: 'San Francisco',
        latitude: 37.7749,
        longitude: -122.4194,
      },
    },
  },
  secrets: {
    account: { api_key: 'test_key' },
  },
  fetchHandlers: [
    {
      match: 'https://api.acme.example',
      respond: () => Response.json({ temperature: 12.4, units: 'celsius' }),
    },
  ],
});

const result = await integration.tools[0].run(ctx, { units: 'celsius' });
expect(result).toEqual({ temperature: 12.4, units: 'celsius' });
```

## Troubleshooting

### Tool not appearing in assistant

Check:

* Tool name matches `^[a-z][a-z0-9_]*$`
* No duplicate names in your `tools` array
* `proposes.credentialModel` is declared if the tool is for a workflow
* The integration is activated on the account

### defineIntegration() rejects your parameters

Your `parameters` object has a key other than `type`, `properties`, `required`, or `additionalProperties` at the root (`title`, `description`, `$schema`, …), or a root `type` other than `'object'`. The root is `.strict()`, so the error points at `tools.<index>.parameters`. Remove the key, or move a description into the relevant property schema.

## Next steps

* [Capability context](/sdk/capability-context) — use ctx in tools
* [Testing](/sdk/testing) — write unit tests for tools
* [Proposes](/sdk/surfaces/proposes) — declare tool classifications for workflows


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.