Skip to main content

Overview

Tools are executable functions that provide specific functionality within your plugin. They can perform actions, fetch data, process information, or interact with external services. Each tool has a well-defined schema and an invoke function that executes the tool’s logic.

Defining Tools

Use the addTool method to register a tool definition:
plugin.addTool({
  name: "send_message",
  display_name: { 
    en_US: "Send Message" 
  },
  description: { 
    en_US: "Send a message to a Slack channel" 
  },
  parameters: {
    type: "object",
    properties: {
      channel: {
        type: "string",
        description: "The channel ID to send the message to"
      },
      message: {
        type: "string",
        description: "The message content"
      }
    },
    required: ["channel", "message"]
  },
  credentials: ["slack_api_token"],
  invoke: async ({ args }) => {
    const { credentials, parameters } = args
    const token = credentials.slack_api_token.token
    
    const response = await fetch("https://slack.com/api/chat.postMessage", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${token}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        channel: parameters.channel,
        text: parameters.message
      })
    })
    
    const data = await response.json()
    
    if (!data.ok) {
      throw new Error(`Failed to send message: ${data.error}`)
    }
    
    return {
      success: true,
      message_id: data.ts,
      channel: data.channel
    }
  }
})

Tool Schema

name
string
required
Unique identifier for the tool. Used for invocation and must be unique within the plugin.
display_name
object
required
Localized display names shown to users in the UI.
description
object
required
Localized descriptions explaining what the tool does.
parameters
JSONSchema
required
JSON Schema defining the input parameters for the tool. Must be a valid JSON Schema object.
credentials
string[]
Array of credential names required by this tool. References credentials defined with addCredential.
invoke
function
required
The function that executes the tool’s logic. This is where your tool’s actual implementation lives.

The Invoke Function

The invoke function is the heart of your tool. It receives arguments and returns results.

Function Signature

invoke: async ({ args }: {
  args: {
    parameters: Record<string, any>
    credentials?: Record<string, any>
  }
}) => Promise<any>

Parameters

args.parameters
object
required
The input parameters for the tool, validated against your parameters schema.
args.credentials
object
Credential data for any credentials listed in the tool’s credentials array. Keys are credential names.

Return Value

You can return any JSON-serializable data from the invoke function:
// Simple success indicator
return { success: true }

// Data objects
return { 
  users: [...],
  total: 42,
  page: 1
}

// Rich results with metadata
return {
  data: { /* ... */ },
  metadata: {
    timestamp: Date.now(),
    duration_ms: 150
  }
}
The invoke function must return JSON-serializable data. Functions, circular references, and other non-serializable values will cause errors.

Tool Invocation Flow

When a user or AI agent triggers your tool:
  1. Hub Server sends invoke_tool message to your plugin
  2. Plugin resolves the tool definition from the registry
  3. Plugin validates the message and extracts parameters
  4. Plugin executes the tool’s invoke function
  5. Plugin sends response back to Hub Server:
    • invoke_tool_response on success
    • invoke_tool_error on failure
// Internal SDK implementation (for reference)
channel.on("invoke_tool", async (message) => {
  const { request_id, tool_name, parameters, credentials } = message
  
  try {
    const definition = registry.resolve("tool", tool_name)
    const result = await definition.invoke({ 
      args: { credentials, parameters } 
    })
    
    channel.push("invoke_tool_response", { 
      request_id, 
      data: result 
    })
  } catch (error) {
    channel.push("invoke_tool_error", { 
      request_id, 
      message: error.message 
    })
  }
})

Parameter Schemas

Define clear, well-documented parameters using JSON Schema:

Simple Parameters

parameters: {
  type: "object",
  properties: {
    query: { 
      type: "string",
      description: "Search query" 
    },
    limit: { 
      type: "number",
      description: "Maximum number of results",
      default: 10,
      minimum: 1,
      maximum: 100
    }
  },
  required: ["query"]
}

Complex Parameters

parameters: {
  type: "object",
  properties: {
    filters: {
      type: "object",
      properties: {
        status: { 
          type: "string",
          enum: ["active", "inactive", "pending"] 
        },
        created_after: { 
          type: "string",
          format: "date-time" 
        },
        tags: { 
          type: "array",
          items: { type: "string" } 
        }
      }
    },
    sort_by: {
      type: "string",
      enum: ["name", "date", "status"],
      default: "date"
    },
    sort_order: {
      type: "string",
      enum: ["asc", "desc"],
      default: "desc"
    }
  }
}

Array Parameters

parameters: {
  type: "object",
  properties: {
    recipients: {
      type: "array",
      items: {
        type: "object",
        properties: {
          email: { type: "string", format: "email" },
          name: { type: "string" }
        },
        required: ["email"]
      },
      minItems: 1,
      maxItems: 50
    }
  },
  required: ["recipients"]
}

Working with Credentials

Tools can require one or more credentials:

Single Credential

plugin.addTool({
  name: "get_user",
  credentials: ["github_api_key"],
  invoke: async ({ args }) => {
    const apiKey = args.credentials.github_api_key.api_key
    // Use the API key...
  }
})

Multiple Credentials

plugin.addTool({
  name: "sync_data",
  credentials: ["source_api", "destination_api"],
  invoke: async ({ args }) => {
    const sourceKey = args.credentials.source_api.api_key
    const destKey = args.credentials.destination_api.api_key
    
    // Fetch from source
    const data = await fetchData(sourceKey)
    
    // Push to destination
    await pushData(destKey, data)
    
    return { synced: data.length }
  }
})

Optional Credentials

Credentials are always optional at the tool level. Handle cases where credentials might not be provided:
invoke: async ({ args }) => {
  const { credentials, parameters } = args
  
  if (!credentials?.api_key) {
    throw new Error("API key credential is required")
  }
  
  // Continue with credential
}

Error Handling

Implement comprehensive error handling in your invoke functions:

Throwing Errors

invoke: async ({ args }) => {
  const { parameters } = args
  
  // Validate input
  if (!parameters.email.includes('@')) {
    throw new Error("Invalid email address format")
  }
  
  try {
    const response = await fetch(apiUrl)
    
    if (!response.ok) {
      throw new Error(`API returned ${response.status}: ${response.statusText}`)
    }
    
    return await response.json()
  } catch (error) {
    // Network or parsing error
    throw new Error(`Failed to fetch data: ${error.message}`)
  }
}

Error Response

When your invoke function throws an error, the SDK automatically:
  1. Catches the error
  2. Sends invoke_tool_error message to Hub Server
  3. Includes the error message and request ID
// SDK handles this automatically
catch (error) {
  if (error instanceof Error) {
    channel.push("invoke_tool_error", { 
      request_id, 
      message: error.message 
    })
  } else {
    channel.push("invoke_tool_error", { 
      request_id, 
      message: "Unexpected Error" 
    })
  }
}

Schema Validation

Tool definitions are validated using the ToolDefinitionSchema from @choiceopen/atomemo-plugin-schema:
import { ToolDefinitionSchema } from "@choiceopen/atomemo-plugin-schema/schemas"

const definition = ToolDefinitionSchema.parse(tool)
registry.register("tool", definition)
If validation fails, a Zod error will be thrown with details about what’s invalid.

Method Wrapping

The SDK uses a method wrapper to ensure type safety:
const InvokeMethodWrapper = ToolDefinitionSchema.shape.invoke
const invoke = InvokeMethodWrapper.implementAsync(definition.invoke)

const data = await invoke({ args: { credentials, parameters } })
This ensures that your invoke function is called with the correct argument structure.

Example Tools

Data Fetching Tool

plugin.addTool({
  name: "fetch_weather",
  display_name: { en_US: "Fetch Weather" },
  description: { en_US: "Get current weather for a location" },
  parameters: {
    type: "object",
    properties: {
      location: { 
        type: "string",
        description: "City name or zip code" 
      },
      units: {
        type: "string",
        enum: ["metric", "imperial"],
        default: "metric"
      }
    },
    required: ["location"]
  },
  credentials: ["weather_api_key"],
  invoke: async ({ args }) => {
    const { credentials, parameters } = args
    const apiKey = credentials.weather_api_key.key
    
    const response = await fetch(
      `https://api.weather.com/v3/weather?` +
      `location=${encodeURIComponent(parameters.location)}&` +
      `units=${parameters.units}&` +
      `apiKey=${apiKey}`
    )
    
    if (!response.ok) {
      throw new Error("Failed to fetch weather data")
    }
    
    const weather = await response.json()
    
    return {
      temperature: weather.temperature,
      condition: weather.condition,
      humidity: weather.humidity,
      location: parameters.location
    }
  }
})

Data Processing Tool

plugin.addTool({
  name: "analyze_text",
  display_name: { en_US: "Analyze Text" },
  description: { en_US: "Perform sentiment analysis on text" },
  parameters: {
    type: "object",
    properties: {
      text: { 
        type: "string",
        description: "Text to analyze" 
      },
      language: {
        type: "string",
        enum: ["en", "es", "fr", "de"],
        default: "en"
      }
    },
    required: ["text"]
  },
  invoke: async ({ args }) => {
    const { parameters } = args
    
    // Perform analysis (simplified example)
    const sentiment = analyzeSentiment(parameters.text)
    const keywords = extractKeywords(parameters.text)
    
    return {
      sentiment: sentiment.score,
      sentiment_label: sentiment.label,
      keywords: keywords,
      word_count: parameters.text.split(/\s+/).length,
      language: parameters.language
    }
  }
})

Best Practices

Even though parameters are validated against your schema, add additional validation in your invoke function for complex business logic.
Clear descriptions help AI agents understand how to use your tools effectively.
Always catch errors and provide meaningful error messages that help users understand what went wrong.
Return well-structured, documented data that’s easy to consume and process.
Each tool should do one thing well. Create multiple tools rather than one tool with many modes.
Provide sensible defaults for optional parameters to improve usability.

Next Steps

Credentials

Learn how to define credentials for your tools

Models

Define AI models for your plugin

Registry

Understand how tools are registered and resolved

Build docs developers (and LLMs) love