LLMs Technical Reviews

ComposioHQ/composio

Python and TypeScript client SDKs, provider adapters and CLI for Composio's hosted tool-execution, auth and trigger API.

GitHub ↗★ 30kTypeScriptMITcommit 900bbb3 · 2026-10-05homepage ↗

Overview

Composio is a hosted connector platform for AI agents: a catalog of about a thousand third-party “toolkits”, managed per-user OAuth and API-key connections, event triggers and a remote code sandbox. This repository is the open client side: the TypeScript SDK (@composio/core), the Python SDK (composio), framework adapters (“providers”), an Effect-based CLI and the docs site.

Tool definitions, credential storage, token exchange, execution, search and trigger delivery all live in Composio’s backend. Both SDKs wrap a generated HTTP client (@composio/client, composio_client), and the repo contains no toolkit definitions or execution engine. What it does contain is careful client logic: per-framework schema conversion, execution hooks, file staging, webhook verification, Pusher-based realtime triggers, and an in-process path for your own custom tools.

The v3 SDK is built around sessions. composio.create(userId, config) creates a Tool Router session that gives the agent a few meta tools (search, multi-execute, connection management, sandbox) instead of hundreds of schemas, and can also be exported as a hosted MCP endpoint. The older tools.get / tools.execute path still exists alongside it.

Architecture

flowchart LR
  APP["Your agent code"] --> SDK["Composio class (TS / Python)"]
  SDK --> SESS["Sessions / ToolRouter"]
  SDK --> TOOLS["Tools: get / execute / proxy"]
  SDK --> AUTH["ConnectedAccounts, AuthConfigs"]
  SDK --> TRIG["Triggers, Webhooks"]
  SESS --> RS["ToolRouterSession"]
  RS --> LOCAL["Custom tools (in-process)"]
  RS --> PROV["Provider.wrapTools"]
  TOOLS --> PROV
  PROV --> FW["OpenAI / Anthropic / Vercel / LangChain ..."]
  RS --> GEN["Generated HTTP client"]
  TOOLS --> GEN
  AUTH --> GEN
  TRIG --> GEN
  TRIG --> PUSH["Pusher realtime channel"]
  GEN --> API["Composio backend (hosted)"]
  API --> MCP["Session MCP endpoint"]
  API --> SBX["Remote sandbox"]
Component Path Role
SDK entry (TS) ts/packages/core/src/composio.ts Composio class: resolves keys and scope headers, builds the generated client, wires every model, sets up telemetry
SDK entry (Python) python/composio/sdk.py Same role in Python; wraps composio_client in HttpClient
Sessions ts/packages/core/src/models/ToolRouter.ts, Sessions.ts create / use sessions; builds the backend payload and the MCP config
Session object ts/packages/core/src/models/ToolRouterSession.ts tools(), execute, search, authorize, proxyExecute, multi-execute routing
Tools ts/packages/core/src/models/Tools.ts, python/composio/core/models/tools.py Fetch schemas, apply modifiers, stage files, call tools.execute with retries off
Providers ts/packages/core/src/provider/, ts/packages/providers/*, python/providers/* Convert Composio Tool objects into a framework’s native tool type
Auth models/ConnectedAccounts.ts, AuthConfigs.ts, AuthScheme.ts, Keyring.ts Start and poll connections, import credentials, fetch keyring transfer keys
Triggers models/Triggers.ts, services/pusher/Pusher.ts Trigger instances, webhook verification, realtime subscription
Custom tools models/CustomTool.ts, customToolExecution.ts Zod-defined tools that execute inside your process
CLI ts/packages/cli/ composio search / execute / link / run / generate, built on Effect and Bun
Experimental ts/packages/experimental/ Local sandbox helper, eve and pi agent integrations

How a request flows

The TypeScript session path, from composio.create("user_1") to a tool call:

  1. Construct. new Composio({ apiKey, provider }) resolves the API key and org/project scope, creates the generated client and instantiates every model; composio.create is a bound alias for sessions.create (composio.ts). The default provider is OpenAIProvider.
  2. Bind the provider. The Tools constructor injects an execute function into the provider with _setExecuteToolFn. That function forces dangerouslySkipVersionCheck: true for provider-driven calls (Tools.ts, L846-L854).
  3. Create the session. ToolRouter.create validates the config with Zod, serialises inline custom tools, maps the options to the wire format, calls client.toolRouter.session.create and wraps the result in a ToolRouterSession (ToolRouter.ts).
  4. Get tools. session.tools() fetches the session’s schemas, applies modifySchema, appends custom tools and calls provider.wrapTools with a session-bound execute function (ToolRouterSession.ts).
  5. Convert. For OpenAI, wrapTool emits a type: "function" definition (OpenAIProvider.ts). Agentic providers such as Vercel also embed an execute closure (vercel/src/index.ts).
  6. Model calls a tool. handleToolCalls(session, completion) runs each call in the first choice and returns role: "tool" messages (OpenAIProvider.ts). executeToolForTarget sends a string target to tools.execute and a session target to session.execute (BaseProvider.ts).
  7. Execute. session.execute runs a matching custom tool in-process. Otherwise it posts to client.toolRouter.session.execute with withoutRetries, because a tool call is a non-idempotent write (ToolRouterSession.ts). The backend resolves the connected account and calls the third-party API.

On the direct path, executeComposioTool refuses a resolved toolkit version of "latest" unless dangerouslySkipVersionCheck is set (Tools.ts, L1144-L1161). The default toolkitVersions is 'latest' (ConfigDefaults.node.ts), so a manual tools.execute fails until you pin a version.

Key components

Sessions and meta tools

A session is a server-side object holding the user, allowed toolkits, auth choices and sandbox settings; the backend decides its tool list. The SDK names meta tools such as COMPOSIO_MULTI_EXECUTE_TOOL, and sandbox.enable: false removes COMPOSIO_REMOTE_WORKBENCH and COMPOSIO_REMOTE_BASH_TOOL (toolRouter.types.ts). Search ranking and the sandbox runtime are not in this repo. The session also exposes search, toolkits, authorize(toolkit) and proxyExecute as plain methods.

Hosted MCP export

buildMCPServerConfig attaches credential headers to the session’s MCP config only when the MCP URL shares the API base URL’s origin; with mcp: true a mismatch throws ComposioMCPDestinationError (toolRouterMcp.ts). The MCP server itself runs on Composio’s side.

Custom tools and split multi-execute

createCustomTool(slug, { inputParams: zodSchema, execute }) defines a tool that runs in your process; its definition is sent inline at session creation so the backend can search it. When COMPOSIO_MULTI_EXECUTE_TOOL mixes local and remote tools, routeMultiExecute runs local items in-process, sends the rest to the backend and merges results in order (ToolRouterSession.ts).

Modifiers and file staging

executeWithTool runs file-upload preprocessing, beforeExecute, the API call, then file download and afterExecute (Tools.ts). Auto-upload is opt-in (dangerouslyAllowAutoUploadDownloadFiles) with a directory allowlist; when off, the SDK warns once that an LLM will otherwise invent an s3key (Tools.ts).

Auth and connected accounts

AuthScheme has static builders for ten schemes (OAuth2, OAuth1, API key, Basic, Bearer, service account and others) used to import existing credentials (AuthScheme.ts). initiate and link return a ConnectionRequest whose waitForConnection polls once a second until the account is ACTIVE, fails or times out (ConnectionRequest.ts). Token refresh is server-side; the client refresh() is deprecated (ConnectedAccounts.ts). keyring.listTransferKeys() returns public JWKs for sealing secrets client-side (Keyring.ts).

Triggers

triggers.verifyWebhook checks the webhook-* headers with HMAC-SHA256, enforces a 300-second default tolerance and normalises V1–V3 payloads (Triggers.ts). For realtime delivery, triggers.subscribe joins the Pusher channel private-<projectId>_triggers, filters client-side and reassembles chunked events (Triggers.ts, Pusher.ts). Polling and matching happen on the backend.

Python parity

The Python SDK mirrors TypeScript class for class; Composio.__init__ creates a fresh provider per instance so the injected execute function cannot leak between instances (sdk.py). Session execution also runs without_retries (tools.py). Providers implement wrap_tool / wrap_tools (agentic.py).

Extending it

  • New framework. Subclass BaseNonAgenticProvider (schema only) or BaseAgenticProvider (tools carry an execute closure) (BaseProvider.ts). The repo ships 11 TypeScript and 13 Python provider packages.
  • Your own tools. createCustomTool (TS) or experimental.tool (Python) put local functions in a session.
  • Hooks. modifySchema, beforeExecute, afterExecute and beforeFileUpload.
  • Raw API calls. proxyExecute calls any toolkit endpoint with stored credentials.
  • Typed stubs. composio generate ts|py from the live catalog.
  • New integrations. Not possible here; toolkit definitions are server-side.

Running it

  • Install. pnpm add @composio/core or pip install composio, plus a provider package, and set COMPOSIO_API_KEY. The CLI installs via install.sh and runs on Bun.
  • Required services. The Composio backend, always. COMPOSIO_BASE_URL changes the endpoint, but nothing here implements the API, so it cannot be self-hosted. Realtime triggers need Pusher.
  • Local sandbox (experimental). experimental_createLocalWorkbenchSession returns a Python helper and env vars to run sandbox helpers yourself against a session with the remote sandbox disabled (local-workbench.ts). The code itself warns that this puts your full project API key into the sandbox environment (shim.ts).
  • Runtimes. The TypeScript core has Node and Cloudflare Workers builds.

Strengths and caveats

  • Strength: broad, consistent framework coverage. One Tool shape and provider contract cover about two dozen frameworks in two languages.
  • Strength: defensive client code. No retries on executes, origin-pinned MCP headers, timing-safe webhook HMAC, opt-in file upload with allowlists.
  • Strength: sessions keep context small. Meta tools reach the whole catalog without loading every schema into the prompt.
  • Caveat: it is a client, not a platform. Execution, credentials, toolkits, search, triggers and sandbox are hosted and proprietary; MIT covers only the SDKs.
  • Caveat: version-pinning friction. Manual tools.execute refuses the default latest version while provider-driven calls silently skip the check, so the same tool behaves differently by call path.
  • Caveat: two APIs at once. Sessions and the older tools.get / tools.execute path coexist with deprecated aliases (toolRouter, workbench), so the surface is large.
  • Caveat: telemetry by default. allowTracking defaults to true, and the TS SDK checks npm for updates at start-up.

Sources: code at 900bbb3, deepwiki-open wiki (11 pages), OpenDeepWiki wiki (23 pages), verified Q&A.

How it answers the API layer & connectors questions

Each answer was drafted by a code-reading agent at commit 900bbb3. Its citations were checked mechanically. Compare with the other api layer & connectors →

How is third-party authentication implemented?

answered

Authentication in Composio supports 11 auth schemes defined in both SDKs. The AuthScheme class (Python: python/composio/core/models/connected_accounts.py:123-353, TS: ts/packages/core/src/models/AuthScheme.ts:8-206) provides factory methods for OAuth2, OAuth1, API_KEY, BASIC, BEARER_TOKEN, BASIC_WITH_JWT, GOOGLE_SERVICE_ACCOUNT, COMPOSIO_LINK, CALCOM_AUTH, BILLCOM_AUTH, and NO_AUTH. OAuth2/1 flows are redirect-based: ConnectedAccounts.initiate() (Python: python/composio/core/models/connected_accounts.py:589-732) begins the flow and returns a ConnectionRequest with a redirect_url, while wait_for_connection() polls until the connection is ACTIVE or enters a terminal state. The newer link() method (Python: python/composio/core/models/connected_accounts.py:734-839) creates a Composio Connect Link and is the recommended path for Composio-managed OAuth. For custom OAuth apps, complete_auth() (Python: python/composio/core/models/connected_accounts.py:462-490) redeems a session URI after the user's identity is verified. Connected accounts are multi-tenant: each is scoped to a user_id and auth_config_id. ACLs on SHARED connections allow per-user allow/deny lists via update_acl() (Python: python/composio/core/models/connected_accounts.py:522-587). Credential encryption uses JWEs via the organization's customer-managed keyring: Keyring.listTransferKeys() (TS: ts/packages/core/src/models/Keyring.ts:56-64) returns RSA-OAEP-256/A256GCM public keys for sealing secrets client-side before sending them to Composio. Token refresh on connected accounts is deprecated; the API now recommends re-initiating auth instead.

How is an integration / connector defined?

answered

Integrations are called toolkits in Composio. Each toolkit is a collection of related tools (e.g., GitHub, Slack, Gmail) identified by a unique slug. The SDKs do not define toolkits in code — they are defined server-side on the Composio backend and fetched at runtime. The Toolkits model class (Python: python/composio/core/models/toolkits.py:33-307) provides list(), get(slug:), get_many(slugs:), and methods like recommend_scopes() and list_grant_contexts() for OAuth scope recommendations. The docs generation script docs/scripts/generate-toolkits.ts:1-80 reveals the scale: it issues ~6500 requests across the catalog fetching metadata for ~2100 toolkits. Each toolkit carries name, description, auth_config_details (which auth modes the toolkit supports and what fields each requires), a list of tools, and optional triggers. Versioning is explicit: you can set SDK-level toolkitVersions (a dict mapping toolkit slugs to version strings like "20250909_00") and the backend manages a full changelog accessible via changelog() (Python: python/composio/core/models/toolkits.py:129-142). The docs site fetches the raw OpenAPI spec from https://backend.composio.dev/api/v3/openapi.json for its API reference (see docs/scripts/fetch-openapi.mjs) but that is for documentation, not for codegen-ing toolkit definitions. OpenAPI codegen is not present in this repo for toolkit creation — it is a server-side process.

How are integrations exposed to LLM agents?

answered

Tools are exposed to LLM agents via two mechanisms. Provider adapters convert Composio's generic Tool schema into the native tool format for a specific agent framework. The SDK ships ~14 Python providers (python/providers/: openai, anthropic, langchain, crewai, autogen, google, gemini, llamaindex, etc.) and ~13 TypeScript providers (ts/packages/providers/). Each subclasses BaseProvider (Python: python/composio/core/provider/base.py:62-100) and implements wrap_tools() to transform tool schemas — for example, OpenAIProvider.wrap_tool() (Python: python/composio/core/provider/_openai.py:30-42) maps each tool to an OpenAI ChatCompletionToolParam with type: "function" and a FunctionDefinition. The second mechanism is MCP (Model Context Protocol): every session optionally exposes a hosted MCP endpoint (composio.create(userId, { mcp: true }) returns a session with session.mcp.url and session.mcp.headers), allowing any MCP-capable client (Claude Desktop, Cursor) to connect directly. For dynamic tool loading, sessions use a Tool Router pattern: calling session.tools() without filtering returns meta-tools that let the agent search for, authenticate, and execute tools at runtime without loading hundreds of definitions into context. The meta-tools include search tools, auth-config listing, and connected-account management — the agent discovers what it needs on demand.

How is a tool call executed?

answered

Tool execution is always proxied through the Composio backend — there is no local sandboxed execution in this repo. Execution happens via the Tools class (Python: python/composio/core/models/tools.py:135-831). Two paths exist: direct execution via tools.execute(slug, arguments, ...) which calls the Composio API's POST /tools/execute endpoint (Python: python/composio/core/models/tools.py:660-791), and session execution via session.execute(toolSlug, arguments) which routes through the Tool Router session's endpoint (Python: python/composio/core/models/tools.py:511-604). The session path resolves authentication and connection management automatically. Retries are intentionally disabled for both paths (Python: python/composio/core/models/tools.py:569-570) — tool execution is a non-idempotent write, and retrying after a timeout could duplicate side effects. Tool versioning is strict: execution requires a pinned version string (e.g., "20250909_00") or explicit dangerously_skip_version_check=True; calling with version "latest" raises a ToolVersionRequiredError (Python: python/composio/core/models/tools.py:636-637). The proxy mechanism (python/composio/core/models/tools.py:792-817) lets callers send raw HTTP requests to any toolkit's base URL, authenticated with the connected account's credentials. File handling is opt-in via dangerously_allow_auto_upload_download_files: when enabled, the SDK stages local files to S3 before execution and downloads result files afterward. Schema modifiers (before_execute, after_execute, before_file_upload) allow interposition on the execution pipeline. There is no rate limiting, pagination handling, or error mapping visible in the SDK itself — those are backend concerns.

Editor's note. Correction: the version pin only applies to manual tools.execute calls. Provider-wrapped calls force dangerouslySkipVersionCheck=true, and session executions skip the check entirely. Not all execution is remote either: custom tools (createCustomTool / experimental.tool) run in-process, and COMPOSIO_MULTI_EXECUTE_TOOL batches are split between local and backend execution.

How are data sync, webhooks and triggers implemented?

answered

Composio implements triggers through a combination of trigger instances, webhook subscriptions, and realtime delivery. Trigger instances are created per user per toolkit via Triggers.create(userId, slug, config) (TS: ts/packages/core/src/models/Triggers.ts:240-296, Python: python/composio/core/models/triggers.py:1271-1324). They represent a subscription to a specific event type (e.g., GITHUB_COMMIT_EVENT). The backend resolves the user's connected account automatically or you can pin a specific connected_account_id. Webhook subscriptions are project-scoped: triggers.setWebhookSubscription({ webhookUrl }) (TS: ts/packages/core/src/models/Triggers.ts:178-180, Python: python/composio/core/models/triggers.py:1162-1184) creates or updates the outbound delivery endpoint. Composio sends events as HTTP POSTs with HMAC-SHA256 signatures (webhook-id, webhook-timestamp, webhook-signature headers). The SDK provides verify_webhook() (Python: python/composio/core/models/triggers.py:1351-1420) and parse() (Python: python/composio/core/models/triggers.py:1422-1539) for server-side verification. Three webhook payload versions are supported: V1 (legacy trigger_name/connection_id), V2 (type/timestamp/data), and V3 (unified composio.* event envelope with structured metadata). Inbound endpoints for third-party providers are managed via webhooks.endpoints.create() (TS: ts/packages/core/src/models/Webhooks.ts:394-412), which creates an ingress URL per toolkit+OAuth-app pair. Realtime delivery uses Pusher WebSockets: triggers.subscribe(callback) (TS: ts/packages/core/src/models/Triggers.ts:543-575, Python: python/composio/core/models/triggers.py:1326-1349) opens a persistent WebSocket connection to the private-{project_id}_triggers channel and delivers normalized TriggerEvent objects. Client-side filters by trigger_slug, toolkit, user_id, connected_account_id, etc., can be applied. Chunked trigger events (for large payloads) are reassembled client-side.

How is it self-hosted and what is open vs proprietary?

answered

The SDK is MIT-licensed (LICENSE) and fully open source. Every component in this repository — the Python SDK (python/composio/), the TypeScript SDK (ts/packages/core/), the CLI (ts/packages/cli/), and all provider adapters (python/providers/, ts/packages/providers/) — is visible and auditable. However, the execution backend is proprietary and hosted at https://backend.composio.dev. The SDK is purely a client: every tools.execute(), connected_accounts.initiate(), triggers.create(), and toolkits.list() call goes to the Composio API. There is no self-hosted backend, no local execution engine, and no offline mode. The toolkit definitions (~2100 toolkits) live server-side; the SDK fetches them at query time. Authentication (OAuth token exchange, credential storage) also happens server-side; the SDK never sees or stores provider credentials — it only receives session tokens. The COMPOSIO_API_KEY (or user/org API keys) is required for all operations. The CLI can be installed standalone (curl -fsSL https://composio.dev/install | sh), but it also requires network access to the Composio API. There is no enterprise/on-premises distribution or self-hosted container available in this repository. The COMPOSIO_BASE_URL config option allows pointing at a different API environment (staging, production), but all available targets are controlled by Composio.