Toolkits

Custom Tools and Toolkits (Experimental)

Markdown

Custom tool APIs are experimental and may change in future releases.

Custom tools work with native tools (session.tools()). MCP support is coming soon. Custom tools are not available via the MCP server URL yet.

Custom tools let you define tools that run in-process alongside remote Composio tools within a session. There are three patterns:

  • Standalone tools - for internal app logic that doesn't need Composio auth (DB lookups, in-memory data, business rules)
  • Extension tools - wrap a Composio toolkit's API with custom business logic via extendsToolkit / extends_toolkit, using ctx.proxyExecute() / ctx.proxy_execute() for authenticated requests
  • Custom toolkits - group related standalone tools under a namespace

Install

npm install @composio/core zod
pip install composio

Initialize the client

import { Composio } from "@composio/core";

const composio = new Composio({ apiKey: "your_api_key" });
from composio import Composio

composio = Composio(api_key="your_api_key")

Create the tool

A standalone tool handles internal app logic that doesn't need Composio auth. ctx.userId identifies which user's session is running.


const profiles: Record<string, { name: string; email: string; tier: string }> = {
  "user_1": { name: "Alice Johnson", email: "alice@myapp.com", tier: "enterprise" },
  "user_2": { name: "Bob Smith", email: "bob@myapp.com", tier: "free" },
};

const getUserProfile = experimental_createTool("GET_USER_PROFILE", {
  name: "Get user profile",
  description: "Retrieve the current user's profile from the internal directory",
  inputParams: z.object({}),
  execute: async (_input, ctx) => {
    const profile = profiles[ctx.userId];
    if (!profile) throw new Error(`No profile found for user "${ctx.userId}"`);
    return profile;
  },
});
from pydantic import BaseModel, Field

from composio import Composio

composio = Composio(api_key="your_api_key")


class UserLookupInput(BaseModel):
    user_id: str = Field(description="User ID")


USERS = {
    "user_1": {"name": "Alice Johnson", "email": "alice@myapp.com", "tier": "enterprise"},
    "user_2": {"name": "Bob Smith", "email": "bob@myapp.com", "tier": "free"},
}


@composio.experimental.tool()
def get_user_profile(input: UserLookupInput, ctx):
    """Retrieve the current user's profile from the internal directory."""
    profile = USERS.get(input.user_id)
    if not profile:
        raise ValueError(f'No profile found for user "{input.user_id}"')
    return profile

Bind to a session

Pass custom tools via the experimental option. session.tools() returns both remote Composio tools and your custom tools.


const session = await composio.create("user_1", {
  experimental: {
    customTools: [getUserProfile],
  },
});

const tools = await session.tools();
from composio import Composio

composio = Composio(api_key="your_api_key")

session = composio.create(
    user_id="user_1",
    experimental={
        "custom_tools": [get_user_profile],
    },
)

tools = session.tools()

Meta tools integration

Custom tools work automatically with Composio's meta tools:

Meta toolBehavior
COMPOSIO_SEARCH_TOOLSIncludes custom tools in search results, with slight priority for tools that don't require auth
COMPOSIO_GET_TOOL_SCHEMASReturns schemas for custom tools alongside remote tools
COMPOSIO_MULTI_EXECUTE_TOOLRuns custom tools in-process while remote tools go to the backend, merging results transparently
COMPOSIO_MANAGE_CONNECTIONSHandles auth for extension tools. If a tool extends gmail, the agent can prompt the user to connect Gmail

Custom tools are not supported in Workbench.

Context object (ctx)

Every custom tool's execute function receives (input, ctx). Use ctx to access the current user, make authenticated API requests, or call other Composio tools.

Property / MethodDescription
ctx.userIdThe user ID for the current session
ctx.proxyExecute({ toolkit, endpoint, method, body?, parameters? })Make an authenticated HTTP request via Composio's auth layer
ctx.execute(toolSlug, args)Execute any Composio native tool from within your custom tool
Property / MethodDescription
ctx.user_idThe user ID for the current session
ctx.proxy_execute(toolkit, endpoint, method, body=None, parameters=[])Make an authenticated HTTP request via Composio's auth layer
ctx.execute(tool_slug, arguments)Execute any Composio native tool from within your custom tool

See the full API in the SDK reference: TypeScript | Python

Verifying registration

Use these methods to list registered tools and toolkits. Slugs include their final LOCAL_ prefix, and toolkit-scoped tools also include the toolkit slug.

const customTools = session.customTools();
const customToolkits = session.customToolkits();
custom_tools = session.custom_tools()
custom_toolkits = session.custom_toolkits()

Programmatic execution

Use session.execute() to run custom tools directly, outside of an agent loop. Custom tools execute in-process; remote tools are sent to the backend automatically.

const result = await session.execute("GET_USER_PROFILE");
result = session.execute("GET_USER_PROFILE")

Best practices

Naming and descriptions

The agent relies on your tool's name and description to decide when to call it. Be specific: "Send weekly promo email" is better than "Send email". Include what the tool does, when to use it, and what it returns.

In TypeScript, use uppercase slugs like SEND_PROMO_EMAIL. In Python, slugs are inferred from the function name, so snake_case produces clean defaults. You can also pass slug and name explicitly.

Accessing authenticated APIs

If your tool needs to call an API that requires user credentials (Gmail, GitHub, etc.), set extendsToolkit / extends_toolkit to the toolkit name. Composio will handle authentication automatically, and the agent can prompt users to connect their account if needed.

Defining inputs in Python

Your tool's first parameter must be a Pydantic BaseModel. The field descriptions become what the agent sees as the input schema, and the function's docstring becomes the tool description. You can override this by passing description explicitly.

Tool names get prefixed

Slugs exposed to the agent are automatically prefixed with LOCAL_ and the toolkit name (if applicable):

  • GET_USER_PROFILE becomes LOCAL_GET_USER_PROFILE
  • ASSIGN_ROLE in USER_MANAGEMENT becomes LOCAL_USER_MANAGEMENT_ASSIGN_ROLE

Your slugs cannot start with LOCAL_. This prefix is reserved.

For more best practices, see How to Build Tools for AI Agents: A Field Guide.