How to Create Custom Tools with Custom Logic in the Composio SDK

You can create custom tools in the Composio SDK by defining a tool metadata object with a Zod input schema and an async execution function, then registering it via composio.tools.createCustomTool().

The Composio SDK allows developers to extend the framework's built-in toolset by implementing custom logic that integrates seamlessly with AI agents and workflows. Whether you need to perform domain-specific calculations or interact with proprietary APIs, custom tools provide a standardized way to encapsulate your business logic while maintaining the same ergonomic interface as native Composio tools.

What Are Custom Tools in Composio?

A custom tool is a user-defined Tool object that you register at runtime with the Composio SDK. According to the source code in ts/packages/core/src/models/Tools.ts, each custom tool requires four core components:

  • Metadataslug, name, description, and optional toolkitSlug or connectedAccountId
  • Input schema – A Zod object (inputParams) that validates and describes the tool's arguments
  • Execution function – An async callback that receives parsed input and returns a ToolExecutionResult
  • Registration – A call to CustomToolsService.createTool via the public createCustomTool method

Prerequisites and Setup

Before creating custom tools, ensure you have the Composio SDK installed and configured:

npm install @composio/core zod dotenv

Initialize the SDK with your API key:

import { Composio } from '@composio/core';
import 'dotenv/config';

const composio = new Composio({
  apiKey: process.env.COMPOSIO_API_KEY,
});

Creating a Custom Tool Step-by-Step

Define Tool Metadata

Start by defining the basic metadata that identifies your tool. The slug must be unique and is used to invoke the tool later:

const customToolSlug = 'CALCULATE_SQUARE_OF_A_NUMBER';

Configure the Input Schema with Zod

Use Zod to define the input parameters. This schema validates arguments at runtime and generates JSON schemas for AI model consumption:

import { z } from 'zod';

const inputParams = z.object({
  number: z.number().describe('The number to square'),
});

Implement the Execution Function

The execute function contains your custom logic. It receives the validated input object and optionally a connectionConfig if the tool uses connected accounts. It must return a ToolExecutionResult with data, error, and successful properties:

const execute = async (input: { number: number }) => {
  const { number } = input;
  const result = Number(number) * Number(number);
  return { 
    data: { result }, 
    error: null, 
    successful: true 
  };
};

Registering and Executing Custom Tools

Combine all components and register the tool using createCustomTool, as implemented in src/models/Tools.ts:

const tool = await composio.tools.createCustomTool({
  slug: customToolSlug,
  name: 'Calculate Square',
  description: 'Returns the square of a given number',
  inputParams,
  execute,
});

console.log('✅ Created tool:', tool);

Execute the tool using the standard execute method, passing the slug and arguments:

const result = await composio.tools.execute(customToolSlug, {
  arguments: { number: 3 },
  userId: 'default',
});

console.log('🔎 Execution result:', result);

This example follows the pattern shown in ts/examples/custom-tools/src/simple.ts.

Advanced Example: Integrating with External APIs

Custom tools can leverage connected accounts to authenticate with external APIs. This example from the source analysis demonstrates creating a GitHub gist using an authenticated connection:

const gitHubTool = await composio.tools.createCustomTool({
  slug: 'CREATE_GIST',
  name: 'Create GitHub Gist',
  description: 'Creates a public gist on GitHub',
  toolkitSlug: 'github',          // associate with the GitHub toolkit
  connectedAccountId: '123',      // optional – ties to a specific user connection
  inputParams: z.object({
    filename: z.string(),
    content: z.string(),
  }),
  execute: async (input, connectionConfig) => {
    // `connectionConfig` contains the OAuth token for the linked GitHub account
    const { filename, content } = input;
    const response = await fetch('https://api.github.com/gists', {
      method: 'POST',
      headers: {
        Authorization: `token ${connectionConfig.accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        public: true,
        files: { [filename]: { content } },
      }),
    });
    const data = await response.json();
    return { data, error: null, successful: response.ok };
  },
});

The connectionConfig parameter provides access tokens and credentials associated with the connectedAccountId, enabling secure API interactions without hardcoding secrets.

Summary

  • Custom tools extend the Composio SDK by wrapping your own business logic in the standard Tool interface.
  • Core components include metadata (slug, name, description), a Zod input schema (inputParams), and an async execute function returning ToolExecutionResult.
  • Registration happens via composio.tools.createCustomTool(), implemented in src/models/Tools.ts, which delegates to CustomToolsService.createTool.
  • Execution uses the standard composio.tools.execute(slug, args) method, treating custom and built-in tools identically.
  • Connected accounts can be linked via connectedAccountId and toolkitSlug to provide OAuth tokens to the execution function.

Frequently Asked Questions

What is the difference between custom tools and built-in toolkits?

Built-in toolkits are pre-defined collections of tools maintained by Composio (such as GitHub, Slack, or Gmail integrations) that map to external APIs. Custom tools are user-defined implementations that allow you to execute your own logic, calculations, or proprietary API calls while maintaining the same interface as built-in tools. Both are invoked identically via composio.tools.execute().

Can I use custom tools with connected accounts?

Yes. When creating a custom tool, you can specify connectedAccountId and toolkitSlug in the configuration object. When the tool executes, the execute function receives a connectionConfig parameter containing OAuth tokens and credentials associated with that connected account, enabling secure authentication with external services without exposing secrets in your code.

How does input validation work for custom tools?

Input validation is handled automatically by the Zod schema you provide in the inputParams field. When composio.tools.execute() is called, the SDK validates the arguments against this schema before invoking your execute function. If validation fails, the SDK returns an error response without executing your custom logic, ensuring type safety and preventing malformed data from reaching your business logic.

Where are custom tool definitions stored?

Custom tool definitions are registered at runtime through the createCustomTool method in src/models/Tools.ts. While the SDK stores these definitions in memory for immediate use, the Composio backend can optionally persist them depending on your configuration. The tools remain available for the duration of your SDK session and can be invoked repeatedly using their unique slug identifier.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →