How to Build a Custom Provider for Unsupported AI Frameworks in Composio

Run pnpm create:provider <name> from the project root to scaffold a new package, then implement the wrapTool and wrapTools methods to bridge Composio tools with any external AI SDK.

Composio's extensible SDK architecture allows developers to integrate AI frameworks that aren't shipped out-of-the-box by creating custom providers. A provider is a self-contained package that initializes an external AI client and adapts Composio tools into a format the model can invoke. This guide walks through the exact implementation patterns found in the ComposioHQ/composio source code to add support for any new AI framework.

What Is a Provider?

A provider in Composio is a TypeScript package located under ts/packages/providers/<your-provider>/ that handles three core responsibilities:

  • Initialize the external AI client (e.g., OpenAI, Anthropic, or custom SDKs)
  • Wrap Composio tools so the model can invoke them via wrapTool and wrapTools methods
  • Expose agent-oriented helpers for streaming, tool-calling, or middleware

The provider must return a unified response shape defined in ts/packages/core/src/types/wrapped-tool.ts: { data, successful, error }.

Scaffolding a New Provider

The repository includes a CLI helper that generates boilerplate. Run this from the project root:

pnpm create:provider my-provider

This invokes scripts/create-provider.ts, which creates the folder structure:

After scaffolding, install the external SDK inside the provider folder:

cd ts/packages/providers/my-provider
npm i my-ai-sdk

Implementing the Provider Logic

Initializing the Client

Create a client class in src/client.ts that accepts configuration and builds the external SDK instance:

// ts/packages/providers/myai/src/client.ts
import { MyAISDK } from 'my-ai-sdk';

export interface MyAIConfig {
  apiKey: string;
  endpoint?: string;
}

export class MyAIClient {
  private readonly sdk: MyAISDK;

  constructor(config: MyAIConfig) {
    this.sdk = new MyAISDK({
      apiKey: config.apiKey,
      endpoint: config.endpoint,
    });
  }

  async generate(prompt: string, options?: any): Promise<string> {
    const resp = await this.sdk.chat({ prompt, ...options });
    return resp.output;
  }
}

Implementing wrapTool

The wrapTool method receives a Tool definition (from ts/packages/core/src/types/tool.ts) and returns a callable function. Implement this in src/provider.ts:

// ts/packages/providers/myai/src/provider.ts
import { Tool, WrappedTool } from '@composio/core';
import { MyAIClient, MyAIConfig } from './client';
import { z } from 'zod';

export class MyAIProvider {
  private readonly client: MyAIClient;

  constructor(config: MyAIConfig) {
    this.client = new MyAIClient(config);
  }

  wrapTool(tool: Tool): WrappedTool {
    const validator = tool.inputSchema ?? z.any();

    return async (args: any) => {
      const parsed = validator.parse(args);
      const prompt = `${tool.name}: ${JSON.stringify(parsed)}`;
      const result = await this.client.generate(prompt);
      return { data: result, successful: true, error: null };
    };
  }
}

Implementing wrapTools

Add a convenience method to batch-process tools:

wrapTools(tools: Tool[]): WrappedTool[] {
  return tools.map((t) => this.wrapTool(t));
}

Exporting and Registering

Export the provider in src/index.ts:

// ts/packages/providers/myai/src/index.ts
export { MyAIProvider } from './provider';
export type { MyAIConfig } from './client';

Register the provider in your application:

import { Composio } from '@composio/core';
import { MyAIProvider } from '@composio/provider-myai';

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

const tools = await composio.tools.get('user-id', { toolkits: ['my-toolkit'] });
const wrappedTools = myAI.wrapTools(tools);

Testing Your Provider

Create unit tests in test/provider.test.ts that mock the external SDK:

// ts/packages/providers/myai/test/provider.test.ts
import { MyAIProvider } from '../src';
import { Tool } from '@composio/core';
import { z } from 'zod';

jest.mock('my-ai-sdk', () => ({
  MyAISDK: jest.fn().mockImplementation(() => ({
    chat: jest.fn().mockResolvedValue({ output: 'mocked-response' }),
  })),
}));

describe('MyAIProvider', () => {
  const provider = new MyAIProvider({ apiKey: 'test-key' });
  
  const echoTool: Tool = {
    name: 'ECHO',
    description: 'Returns the input string',
    inputSchema: z.object({ message: z.string() }),
  };

  it('wraps a tool and returns expected data', async () => {
    const wrapped = provider.wrapTool(echoTool);
    const result = await wrapped({ message: 'hello' });
    expect(result).toEqual({
      data: 'mocked-response',
      successful: true,
      error: null,
    });
  });
});

Key Files and Architecture

File Purpose
scripts/create-provider.ts CLI scaffolding script invoked by pnpm create:provider
ts/packages/core/src/types/tool.ts Interface defining the Tool shape
ts/packages/core/src/types/wrapped-tool.ts Return type requiring { data, successful, error }
ts/packages/providers/<name>/src/provider.ts Core provider implementation with wrapTool logic
ts/packages/providers/<name>/src/client.ts External SDK initialization
ts/packages/core/src/composio.ts Main SDK entry point where providers are integrated

Summary

  • Run pnpm create:provider <name> to scaffold boilerplate in ts/packages/providers/<name>/
  • Implement wrapTool to convert Composio tools into callable functions that return { data, successful, error }
  • Use wrapTools to batch-process tool arrays
  • Always validate inputs using the tool's Zod schema (tool.inputSchema)
  • Export the provider from src/index.ts and register it with the Composio class
  • Test using pnpm test:e2e to ensure compatibility across Node, Deno, and Cloudflare runtimes

Frequently Asked Questions

How do I handle streaming responses in a custom provider?

Adapt the return type in wrapTool to match the agentic provider pattern used in packages like @composio/claude-agent-sdk. Instead of returning a resolved value immediately, yield chunks or expose a callback interface that streams tokens back to the caller while maintaining the { data, successful, error } envelope for the final result.

What validation should I implement inside wrapTool?

Reuse the Zod schema provided by the tool definition (tool.inputSchema). Parse and validate arguments with validator.parse(args) before forwarding them to the external SDK. This ensures type safety and consistent error handling across all providers in the Composio ecosystem.

Can I add middleware like logging or retries to my provider?

Yes. Implement middleware inside wrapTools before mapping over the tool array, or add it directly inside wrapTool around the external SDK call. The provider architecture is designed to allow provider-wide concerns like logging, retry logic, or circuit breakers without modifying individual tool definitions.

How do I publish my custom provider once it's complete?

Bump the version in your provider's package.json and run pnpm changeset from the project root to create a release entry. Ensure your code passes pnpm lint and pnpm build compiles without errors. The monorepo's release workflow will handle publishing to NPM when the changeset is merged.

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 →