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

> Learn to build a custom provider for unsupported AI frameworks in Composio. Implement wrapTool and wrapTools methods to connect any AI SDK.

- Repository: [Composio/composio](https://github.com/composiohq/composio)
- Tags: how-to-guide
- Published: 2026-02-19

---

**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`](https://github.com/ComposioHQ/composio/blob/main/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:

```bash
pnpm create:provider my-provider

```

This invokes [`scripts/create-provider.ts`](https://github.com/ComposioHQ/composio/blob/main/scripts/create-provider.ts), which creates the folder structure:

- [`ts/packages/providers/my-provider/src/provider.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/providers/my-provider/src/provider.ts) - Core provider class
- [`ts/packages/providers/my-provider/src/client.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/providers/my-provider/src/client.ts) - External SDK initialization
- [`ts/packages/providers/my-provider/src/index.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/providers/my-provider/src/index.ts) - Barrel exports
- `ts/packages/providers/my-provider/test/` - Unit test directory

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

```bash
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`](https://github.com/ComposioHQ/composio/blob/main/src/client.ts) that accepts configuration and builds the external SDK instance:

```typescript
// 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`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/types/tool.ts)) and returns a callable function. Implement this in [`src/provider.ts`](https://github.com/ComposioHQ/composio/blob/main/src/provider.ts):

```typescript
// 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:

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

```

## Exporting and Registering

Export the provider in [`src/index.ts`](https://github.com/ComposioHQ/composio/blob/main/src/index.ts):

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

```

Register the provider in your application:

```typescript
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`](https://github.com/ComposioHQ/composio/blob/main/test/provider.test.ts) that mock the external SDK:

```typescript
// 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`](https://github.com/ComposioHQ/composio/blob/main/scripts/create-provider.ts) | CLI scaffolding script invoked by `pnpm create:provider` |
| [`ts/packages/core/src/types/tool.ts`](https://github.com/ComposioHQ/composio/blob/main/ts/packages/core/src/types/tool.ts) | Interface defining the `Tool` shape |
| [`ts/packages/core/src/types/wrapped-tool.ts`](https://github.com/ComposioHQ/composio/blob/main/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`](https://github.com/ComposioHQ/composio/blob/main/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`](https://github.com/ComposioHQ/composio/blob/main/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`](https://github.com/ComposioHQ/composio/blob/main/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.