open-wa v5 is alpha. Use v4.76.0 for mature production systems unless you are validating v5.
The Client APIAPI ExplorerLicensing

AI Tools

Register custom AI tools from your plugin for use with MCP and AI agents.

Plugin Ai Tools Wally

AI Tools

Plugins can register custom tools that AI agents can discover and call through the MCP server. This lets your plugin expose WhatsApp functionality to AI agents like Claude, Cursor, and Windsurf.

What Are Plugin Tools

Tools are functions that AI agents can call. Each tool has a name, description, argument schema, and execution function.

ToolDefinition Structure

init: async ({ client }) => ({
  tool: {
    toolName: {
      description: 'What this tool does',
      args: {
        argName: z.string().describe('Description of the argument'),
      },
      execute: async (args, context) => {
        // Tool implementation
        return 'result';
      },
    },
  },
})

Fields

  • description: What the tool does. AI agents use this to decide when to call the tool.
  • args: Zod schema defining the arguments the tool accepts.
  • execute: The function that runs when the tool is called.

ToolContext

The execute function receives a ToolContext with these guaranteed fields:

execute: async (args, context) => {
  context.logger.info('Tool called', { sessionId: context.sessionId });

  if (context.abort.aborted) {
    return 'Tool cancelled before work started.';
  }

  return `Tool ran for session ${context.sessionId}`;
}
  • sessionId: current session ID
  • logger: scoped plugin logger
  • abort: AbortSignal for cancellation

The tool context does not include client. If a tool needs to call WhatsApp methods, capture client from the plugin init() input and use it inside the returned tool handlers.

Example: Lookup Contact Tool

export default createPlugin({
  meta: { name: 'contact-tools' },
  init: async ({ client }) => ({
    tool: {
      lookupContact: {
        description: 'Look up a contact by phone number',
        args: {
          phone: z.string().describe('Phone number to look up'),
        },
        execute: async ({ phone }, context) => {
          if (typeof phone !== 'string') {
            return JSON.stringify({ success: false, error: 'phone must be a string' });
          }

          const chatId = `${phone}@c.us`;
          const contact = await client.getContact(chatId);
          context.logger.info('Contact looked up', { phone });
          return JSON.stringify({ success: true, contact });
        },
      },
    },
  }),
});

Example: Send Message Tool

init: async ({ client }) => ({
  tool: {
    sendMessage: {
      description: 'Send a WhatsApp message to a contact',
      args: {
        phone: z.string().describe('Phone number to send to'),
        message: z.string().describe('Message text'),
      },
      execute: async ({ phone, message }, context) => {
        if (typeof phone !== 'string' || typeof message !== 'string') {
          return JSON.stringify({ success: false, error: 'phone and message must be strings' });
        }

        const chatId = `${phone}@c.us`;
        const messageId = await client.sendText(chatId, message);
        context.logger.info('Message sent', { phone, messageId });
        return JSON.stringify({ success: true, messageId });
      },
    },
  },
})

Best Practices

Descriptive Names

Use clear, action-oriented names like lookupContact not getContact, and sendMessage not send.

Clear Descriptions

Write descriptions that help AI agents decide when to use the tool:

description: 'Look up a contact by phone number. Returns the contact name and profile info.'

Argument Descriptions

Describe each argument so AI agents know what to pass:

args: {
  phone: z.string().describe('Phone number with country code, e.g. 1234567890'),
}

Error Handling

Catch errors and return meaningful results:

execute: async (args, context) => {
  try {
    if (typeof args.chatId !== 'string' || typeof args.message !== 'string') {
      return JSON.stringify({ success: false, error: 'chatId and message must be strings' });
    }

    const messageId = await client.sendText(args.chatId, args.message);
    return JSON.stringify({ success: true, messageId });
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    context.logger.error('Failed to send message', { error: message });
    return JSON.stringify({ success: false, error: message });
  }
}

Return Structured Data

Tool handlers return strings. If you want to return structured data to an agent, serialize a JSON object:

return JSON.stringify({
  success: true,
  data: { name: 'John', phone: '1234567890' },
});
Wally the Walrus typing

Was this helpful?

Wally and his cute companion coffee mug are coding day and night to keep this up-to-date!

On this page