# defineTools

> Create custom tool handlers for agents

The `defineTools` function creates and validates custom tool handlers that agents can use alongside built-in tools. Custom tools are defined in `tools/index.ts` and are available to any agent in the organization.

```typescript
import { defineTools } from 'struere'

export default defineTools([
  {
    name: "send_email",
    description: "Send an email to a recipient",
    parameters: {
      type: "object",
      properties: {
        to: { type: "string", description: "Recipient email address" },
        subject: { type: "string", description: "Email subject line" },
        body: { type: "string", description: "Email body content" },
      },
      required: ["to", "subject", "body"],
    },
    handler: async (args, context, struere, fetch) => {
      const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${process.env.SENDGRID_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          personalizations: [{ to: [{ email: args.to }] }],
          from: { email: "noreply@example.com" },
          subject: args.subject,
          content: [{ type: "text/plain", value: args.body }],
        }),
      })
      return { success: response.ok }
    },
  },
])
```

## Tool Definition

Each tool in the array requires the following fields:

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | `string` | Yes | Unique tool identifier, referenced in agent `tools` arrays |
| `description` | `string` | Yes | Description shown to the LLM for tool selection |
| `parameters` | `ToolParameters` | Yes | JSON Schema defining the tool's input parameters |
| `handler` | `function` | Yes | Async function that executes when the tool is called |
| `templateOnly` | `boolean` | No | If true, tool is only available during system prompt template compilation and not exposed to the LLM at runtime. Defaults to `false`. |

### Validation

`defineTools` throws errors if any tool is missing `name`, `description`, `parameters`, or `handler`.

## Handler Function

The handler function receives four arguments:

```typescript
handler: async (args, context, struere, fetch) => {
  return { result: "value" }
}
```

| Argument | Type | Description |
|----------|------|-------------|
| `args` | `Record<string, unknown>` | Parsed arguments matching the `parameters` schema |
| `context` | `ExecutionContext` | Actor and organization context |
| `struere` | `StruereSDK` | SDK object providing access to all built-in tools |
| `fetch` | `function` | Fetch function restricted to allowed domains |

The handler must return a JSON-serializable value. This value is passed back to the LLM as the tool result.

### ExecutionContext

```typescript
interface ExecutionContext {
  organizationId: string
  actorId: string
  actorType: "user" | "agent" | "system"
}
```

### StruereSDK

The `struere` parameter gives custom tool handlers access to all built-in tools. This enables custom tools to compose platform operations without making raw API calls.

```typescript
import type { StruereSDK } from 'struere'
```

Available namespaces:

| Namespace | Methods |
|-----------|---------|
| `struere.entity` | `create`, `get`, `query`, `update`, `delete`, `link`, `unlink` |
| `struere.event` | `emit`, `query` |
| `struere.agent` | `chat` |

Example using `struere` to create an entity inside a custom tool handler:

```typescript
export default defineTools([
  {
    name: "onboard_student",
    description: "Register a new student and emit an onboarding event",
    parameters: {
      type: "object",
      properties: {
        name: { type: "string" },
        email: { type: "string" },
      },
      required: ["name", "email"],
    },
    handler: async (args, context, struere) => {
      const student = await struere.entity.create({
        type: "student",
        data: { name: args.name, email: args.email },
      })
      await struere.event.emit({
        eventType: "student.onboarded",
        entityId: student.id,
      })
      return { studentId: student.id }
    },
  },
])
```

## Using Custom Tools in Agents

Reference custom tools by their `name` in any agent's `tools` array:

```typescript
import { defineAgent } from 'struere'

export default defineAgent({
  name: "Support Agent",
  slug: "support",
  version: "1.0.0",
  systemPrompt: "You are a customer support agent.",
  tools: [
    "entity.query",
    "entity.update",
    "event.emit",
    "send_email",
  ],
})
```

### Template-Only Tools

Tools defined with `templateOnly: true` are executed during system prompt template compilation but are not exposed to the LLM as callable tools at runtime. This lets you inject dynamic data into system prompts without bloating the agent's tool list.

```typescript
export default defineTools([
  {
    name: 'format_teacher_schedule',
    description: 'Query teachers and format availability into a readable schedule',
    templateOnly: true,
    parameters: {
      type: 'object',
      properties: {},
    },
    handler: async (args, context, struere) => {
      const result = await struere.entity.query({ type: 'teacher' })
      const teachers = Array.isArray(result) ? result : result?.results || []
      return teachers.map((t) => `${t.data?.name}: ${JSON.stringify(t.data?.availability)}`).join('\n')
    },
  },
])
```

Reference the tool in your agent's system prompt using template syntax:

```
{{format_teacher_schedule()}}
```

Template-only tools don't count toward the agent's runtime tool list.

Custom tool names and built-in tool names share the same namespace. Avoid naming conflicts by not using the `entity.`, `event.`, or `agent.` prefixes for custom tools.

For the sandboxed fetch allowlist, execution environment details, and more examples, see [Custom Tools](../tools/custom-tools).
