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.
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
}
}
})
Unique identifier for the tool. Used for invocation and must be unique within the plugin.
Localized display names shown to users in the UI.
Localized descriptions explaining what the tool does.
JSON Schema defining the input parameters for the tool. Must be a valid JSON Schema object.
Array of credential names required by this tool. References credentials defined with addCredential.
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
The input parameters for the tool, validated against your parameters schema.
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.
When a user or AI agent triggers your tool:
Hub Server sends invoke_tool message to your plugin
Plugin resolves the tool definition from the registry
Plugin validates the message and extracts parameters
Plugin executes the tool’s invoke function
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:
Catches the error
Sends invoke_tool_error message to Hub Server
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.
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
}
}
})
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
Validate input parameters
Even though parameters are validated against your schema, add additional validation in your invoke function for complex business logic.
Provide detailed parameter descriptions
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