Files
composio-skills-reference/composio-sdk/rules/app-custom-tools.md
sohamganatra b8b711dff6 Add Composio SDK skill with rules and agent config
Adds composio-sdk/ with SKILL.md, AGENTS.md, and 18 rule files
covering Tool Router, direct execution, triggers, and auth patterns.

Source: composiohq/skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 22:54:21 -08:00

5.0 KiB

title, impact, description, tags
title impact description tags
Creating Custom Tools MEDIUM Build standalone and toolkit-based custom tools with proper authentication and validation
custom-tools
extensibility
authentication
zod
development

Creating Custom Tools

Create your own tools that integrate with Composio:

  • Standalone tools - No external authentication required
  • Toolkit-based tools - Use toolkit credentials for API requests

Standalone Tools

For tools that don't need external authentication:

import { z } from 'zod';

const tool = await composio.tools.createCustomTool({
  slug: 'CALCULATE_SQUARE',
  name: 'Calculate Square',
  description: 'Calculates the square of a number',
  inputParams: z.object({
    number: z.number().describe('The number to calculate the square of'),
  }),
  execute: async (input) => {
    return {
      data: { result: input.number * input.number },
      error: null,
      successful: true,
    };
  },
});

Use for: Math, string operations, data transformations, internal logic.

Toolkit-Based Tools

For tools that call authenticated APIs.

Automatically handles authentication and baseURL:

const tool = await composio.tools.createCustomTool({
  slug: 'GITHUB_STAR_REPOSITORY',
  name: 'Star GitHub Repository',
  toolkitSlug: 'github',
  description: 'Star a repository under composiohq',
  inputParams: z.object({
    repository: z.string().describe('Repository name'),
    page: z.number().optional().describe('Page number'),
  }),
  execute: async (input, connectionConfig, executeToolRequest) => {
    return await executeToolRequest({
      endpoint: `/user/starred/composiohq/${input.repository}`,
      method: 'PUT',
      parameters: [
        {
          name: 'page',
          value: input.page?.toString() || '1',
          in: 'query', // Adds ?page=1
        },
      ],
    });
  },
});

Using connectionConfig (Direct API Calls)

For custom HTTP requests:

const tool = await composio.tools.createCustomTool({
  slug: 'GITHUB_DIRECT_API',
  name: 'Direct GitHub API',
  toolkitSlug: 'github',
  inputParams: z.object({
    repo: z.string().describe('Repository name'),
  }),
  execute: async (input, connectionConfig) => {
    const response = await fetch(`https://api.github.com/repos/${input.repo}`, {
      headers: {
        Authorization: `Bearer ${connectionConfig.val?.access_token}`,
      },
    });

    const data = await response.json();

    return {
      data: data,
      error: response.ok ? null : 'API request failed',
      successful: response.ok,
    };
  },
});

Input Validation with Zod

Define and validate parameters using Zod:

inputParams: z.object({
  // Required string
  name: z.string().describe('User name'),

  // Optional with default
  count: z.number().optional().default(10).describe('Number of items'),

  // With validation
  email: z.string().email().describe('Email address'),

  // Enum
  status: z.enum(['active', 'inactive']).describe('Status'),

  // Array
  tags: z.array(z.string()).describe('Tags'),

  // Nested object
  metadata: z.object({
    key: z.string(),
    value: z.string(),
  }).optional().describe('Metadata'),
})

Always use .describe() - helps AI understand parameter purpose.

Headers and Query Parameters

Add headers and query params via parameters array:

execute: async (input, connectionConfig, executeToolRequest) => {
  return await executeToolRequest({
    endpoint: '/search/repositories',
    method: 'GET',
    parameters: [
      // Query parameters
      {
        name: 'q',
        value: input.query,
        in: 'query', // ?q=value
      },
      // Headers
      {
        name: 'Accept',
        value: 'application/vnd.github.v3+json',
        in: 'header',
      },
    ],
  });
}

Executing Custom Tools

// Standalone tool
await composio.tools.execute('CALCULATE_SQUARE', {
  userId: 'default',
  arguments: { number: 5 },
});

// Toolkit-based tool (uses userId to find account)
await composio.tools.execute('GITHUB_STAR_REPOSITORY', {
  userId: 'user_123',
  arguments: { repository: 'composio' },
});

// With explicit connected account
await composio.tools.execute('GITHUB_STAR_REPOSITORY', {
  userId: 'user_123',
  connectedAccountId: 'conn_abc123',
  arguments: { repository: 'composio' },
});

Error Handling

Always return structured responses:

execute: async (input) => {
  try {
    const result = performOperation(input);
    return {
      data: result,
      error: null,
      successful: true,
    };
  } catch (error) {
    return {
      data: null,
      error: error.message,
      successful: false,
    };
  }
}

Key Points

  • Naming: Use TOOLKIT_ACTION_DESCRIPTION format for slugs
  • Prefer executeToolRequest: Handles auth and baseURL automatically
  • Describe parameters: AI agents need clear descriptions
  • Not persisted: Custom tools exist in memory only, recreate on restart
  • Single toolkit scope: executeToolRequest only works within same toolkit