The hardest part of shipping a useful AI agent in 2026 is not the model — it is everything around it. OAuth flows, refresh tokens, rate limits, webhook signatures, schema drift across hundreds of SaaS APIs. Composio absorbs all of that and gives your agent a single, typed surface: 300+ pre-built tools across Gmail, Slack, GitHub, Notion, Linear, Google Calendar, Stripe, HubSpot, Salesforce, and more.
In this tutorial, you will wire Composio into a TypeScript agent built on the Vercel AI SDK, authenticate a real user against Gmail and GitHub, let the model pick the right tool, and ship it behind a Next.js route.
Why Composio? Building one OAuth integration takes a sprint. Building twenty takes a quarter. Composio gives you all of them — auth, schemas, execution, and event triggers — behind one TypeScript SDK. Your agent code stays focused on prompting and orchestration.
What You Will Learn
By the end of this tutorial, you will be able to:
- Install and configure the Composio TypeScript SDK
- Create auth configs and run a user-facing OAuth connection flow
- List and filter tools by toolkit, action, or use case
- Hand Composio tools to the Vercel AI SDK and the OpenAI Agents SDK
- Listen to real-time triggers (new email, new GitHub issue, Slack message)
- Wrap everything behind a Next.js App Router endpoint
- Apply production patterns: scoped tools, per-user accounts, observability
Prerequisites
Before starting, ensure you have:
- Node.js 20+ installed (
node --version) - Working knowledge of TypeScript and async/await
- A Composio account at app.composio.dev (free tier is enough)
- An OpenAI API key (or any model supported by the Vercel AI SDK)
- A test Gmail account and a GitHub account you control
- A code editor — VS Code or Cursor recommended
Heads up: Composio handles tokens on its side. You never store refresh tokens in your database. The trade-off is that user connections live in Composio — plan your data model accordingly.
How Composio Fits the Agent Stack
A typical agent loop looks like this:
User prompt → LLM → tool call → execute → result → LLM → answer
Without Composio, "execute" means you wrote the integration: OAuth dance, token refresh, request shaping, error mapping. With Composio, "execute" is a single SDK call. The model picks GMAIL_SEND_EMAIL, your agent runtime forwards the call, Composio runs it under the connected user's account, and you get a structured result back.
Architecture
┌──────────────────────────────────────────────┐
│ Your Next.js / Node service │
│ ┌────────────────────────────────────────┐ │
│ │ Vercel AI SDK / OpenAI Agents SDK │ │
│ │ ↑ provides tools │ │
│ │ │ │ │
│ │ Composio SDK │ │
│ └─────────────┬──────────────────────────┘ │
└────────────────┼─────────────────────────────┘
│ HTTPS
┌────────▼─────────┐
│ Composio API │
│ - Auth configs │
│ - Connections │
│ - Tool execution│
│ - Triggers │
└────────┬─────────┘
│
┌─────────────┼──────────────────┐
▼ ▼ ▼
Gmail GitHub Notion ... 300+
The unit of identity in Composio is the connected account — one record per user per toolkit. Your agent always passes a userId so Composio knows which account to use.
Step 1: Project Setup
Create a fresh project and install the SDK.
mkdir composio-agent && cd composio-agent
npm init -y
npm install composio @ai-sdk/openai ai zod dotenv
npm install -D typescript tsx @types/node
npx tsc --init --target es2022 --module nodenext --moduleResolution nodenext --esModuleInterop true --strict trueAdd a .env file:
COMPOSIO_API_KEY=ak_xxx_from_app_composio_dev
OPENAI_API_KEY=sk-xxxGet the Composio key from app.composio.dev/developers.
Create src/composio.ts:
import "dotenv/config";
import { Composio } from "composio";
import { VercelProvider } from "composio/providers/vercel";
export const composio = new Composio({
apiKey: process.env.COMPOSIO_API_KEY!,
provider: new VercelProvider(),
});The provider tells Composio how to format tool schemas for your agent runtime. Composio ships providers for the Vercel AI SDK, OpenAI Agents SDK, LangChain, LlamaIndex, CrewAI, and more.
Step 2: Create an Auth Config
An auth config is your application's identity with a third-party service (the OAuth client ID, scopes, redirect URI). You usually create it once per toolkit in the Composio dashboard, but you can also script it.
Open app.composio.dev, pick the Gmail toolkit, click Setup, and choose Use Composio's OAuth app for development. Composio generates an auth config ID like ac_xxx. Save it.
Do the same for GitHub. You should now have:
GMAIL_AUTH_CONFIG_ID=ac_gmail_xxx
GITHUB_AUTH_CONFIG_ID=ac_github_xxxIn production, replace Composio's shared OAuth app with your own — create a Google Cloud project, register an OAuth client, paste the credentials into Composio. This puts your brand on the consent screen instead of Composio's.
Step 3: Connect a User Account
Connecting an account means walking the user through OAuth. Composio gives you a redirect URL.
Create src/connect.ts:
import { composio } from "./composio.js";
const USER_ID = "user_demo_123"; // your internal user ID
async function connectGmail() {
const connection = await composio.connectedAccounts.initiate(
USER_ID,
process.env.GMAIL_AUTH_CONFIG_ID!,
);
console.log("Open this URL in a browser:");
console.log(connection.redirectUrl);
const account = await connection.waitForConnection(120_000);
console.log("Connected:", account.id, account.status);
}
connectGmail().catch(console.error);Run it:
npx tsx src/connect.tsOpen the printed URL, grant access, and the script resolves with a status: "ACTIVE" connection. Composio now holds the refresh token for user_demo_123 against the Gmail toolkit.
Repeat for GitHub by swapping the auth config ID.
Step 4: Discover the Right Tools
Composio exposes 9,000+ actions. Handing all of them to a model would shred your context window. Filter aggressively.
import { composio } from "./composio.js";
const tools = await composio.tools.get("user_demo_123", {
toolkits: ["gmail", "github"],
limit: 20,
});
for (const tool of Object.values(tools)) {
console.log(tool.name);
}You can also filter by a natural-language use case, which uses Composio's search to surface the most relevant actions:
const tools = await composio.tools.get("user_demo_123", {
useCase: "reply to the latest unread email and create a follow-up GitHub issue",
limit: 8,
});Or by exact action names when you know what you want:
const tools = await composio.tools.get("user_demo_123", {
tools: [
"GMAIL_FETCH_EMAILS",
"GMAIL_SEND_EMAIL",
"GITHUB_CREATE_AN_ISSUE",
],
});Rule of thumb: stay under 20 tools per agent call. Beyond that, accuracy and latency both degrade.
Step 5: Run an Agent with the Vercel AI SDK
Now compose it into a real agent. Create src/agent.ts:
import { generateText, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { composio } from "./composio.js";
const USER_ID = "user_demo_123";
async function run(prompt: string) {
const tools = await composio.tools.get(USER_ID, {
toolkits: ["gmail", "github"],
limit: 12,
});
const result = await generateText({
model: openai("gpt-4.1"),
tools,
stopWhen: stepCountIs(8),
system: [
"You are an assistant that helps the user manage email and GitHub.",
"Always confirm destructive actions in plain language before doing them.",
"When sending email, use a short, professional tone.",
].join("\n"),
prompt,
});
console.log("\n--- ASSISTANT ---\n");
console.log(result.text);
console.log("\n--- STEPS ---\n");
for (const step of result.steps) {
for (const call of step.toolCalls ?? []) {
console.log(` ↳ ${call.toolName}`);
}
}
}
run("Find the three most recent unread emails in my inbox and summarise them.").catch(
console.error,
);Run it:
npx tsx src/agent.tsWhat you should see: the model calls GMAIL_FETCH_EMAILS, receives a structured result, and produces a summary in plain English. Notice that you wrote no Gmail code — no API client, no auth handling, no pagination logic.
Multi-step example
Swap the prompt:
run(
"Find the most recent customer email about a bug, then open a GitHub issue " +
"in the repository 'noqta/example-app' with the subject as the title and the " +
"email body quoted in the description.",
).catch(console.error);The agent will chain GMAIL_FETCH_EMAILS → GITHUB_CREATE_AN_ISSUE in two steps and return the created issue URL.
Step 6: Same Tools, OpenAI Agents SDK
If you prefer the OpenAI Agents SDK, swap the provider — your business logic stays identical.
npm install @openai/agentsimport { Agent, run } from "@openai/agents";
import { Composio } from "composio";
import { OpenAIAgentsProvider } from "composio/providers/openai-agents";
const composio = new Composio({
apiKey: process.env.COMPOSIO_API_KEY!,
provider: new OpenAIAgentsProvider(),
});
const tools = await composio.tools.get("user_demo_123", {
toolkits: ["linear", "slack"],
limit: 10,
});
const agent = new Agent({
name: "ops-assistant",
instructions: "Triage incoming Slack messages and file Linear tickets when appropriate.",
tools,
});
const result = await run(agent, "Check the #support channel for the last 10 messages and create Linear tickets for anything that looks like a bug.");
console.log(result.finalOutput);The Composio provider returns tools in whatever shape the runtime expects. You never re-implement schemas.
Step 7: Real-Time Triggers
Polling email is fine. Reacting to it in real time is better. Composio triggers turn third-party events into webhooks your agent can subscribe to.
Enable a trigger for a user:
const trigger = await composio.triggers.enable("user_demo_123", {
triggerName: "GMAIL_NEW_GMAIL_MESSAGE",
config: { labelIds: ["INBOX"], userId: "me" },
});
console.log("Trigger active:", trigger.id);Now point your Composio project at a webhook URL (Settings → Webhooks). Every new inbox email POSTs JSON to that URL. A minimal handler in a Next.js Route Handler:
// app/api/composio/webhook/route.ts
import { NextResponse } from "next/server";
import { composio } from "@/lib/composio";
export async function POST(req: Request) {
const event = await req.json();
if (event.triggerName !== "GMAIL_NEW_GMAIL_MESSAGE") {
return NextResponse.json({ ok: true });
}
const userId = event.connectedAccount.userId;
const tools = await composio.tools.get(userId, {
toolkits: ["gmail", "linear"],
limit: 6,
});
// ... call your agent here with the event payload as input ...
return NextResponse.json({ ok: true });
}Pair this with the Vercel AI SDK and you have a fully event-driven inbox triage agent that wakes up only when there is work to do.
Step 8: Ship It Behind a Next.js Route
Tie the pieces together in a single App Router endpoint.
// app/api/agent/route.ts
import { generateText, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { Composio } from "composio";
import { VercelProvider } from "composio/providers/vercel";
import { NextResponse } from "next/server";
const composio = new Composio({
apiKey: process.env.COMPOSIO_API_KEY!,
provider: new VercelProvider(),
});
export async function POST(req: Request) {
const { prompt, userId } = await req.json();
const tools = await composio.tools.get(userId, {
toolkits: ["gmail", "github", "linear"],
limit: 15,
});
const result = await generateText({
model: openai("gpt-4.1-mini"),
tools,
stopWhen: stepCountIs(6),
system: "You are the user's ops copilot. Be concise. Never send email without explicit confirmation.",
prompt,
});
return NextResponse.json({
text: result.text,
steps: result.steps.length,
usage: result.usage,
});
}The route accepts a userId so each request runs against the right Composio connected account — the multi-tenant story is built in.
Production Patterns
A short field guide for going past the demo.
Scope tools by intent, not by toolkit
Loading every Gmail action for a "summarise my inbox" agent wastes tokens. Build a small registry of intent → tools:
const INTENT_TOOLS: Record<string, string[]> = {
triage: ["GMAIL_FETCH_EMAILS", "GMAIL_REPLY_TO_THREAD", "LINEAR_CREATE_ISSUE"],
release: ["GITHUB_LIST_PULL_REQUESTS", "SLACK_SEND_MESSAGE", "NOTION_CREATE_PAGE"],
};Confirm destructive actions
Sending email, deleting issues, charging cards — wrap them in a confirmation step. Either prompt the model to confirm, or interpose an approval UI before forwarding the tool call.
Cache tool listings
composio.tools.get hits the Composio API. Cache the result per user/toolkit for a few minutes — list rarely changes, prompt latency drops noticeably.
Observability
Log every toolCalls entry returned by generateText. Pair it with Langfuse or PostHog so you can answer "which tool failed most often last week?" without guessing.
Budgets
Composio bills per execution. Cap your agent with stepCountIs(n) and reject prompts that ask for unbounded sweeps ("read all my emails since 2019").
Testing Your Implementation
- Run
npx tsx src/connect.tsand confirm Gmail returnsstatus: "ACTIVE". - Run
npx tsx src/agent.tswith the inbox-summary prompt. You should see at least oneGMAIL_FETCH_EMAILScall in the steps. - Swap the prompt to the multi-step Gmail-to-GitHub example and confirm an issue is created.
- Start the Next.js dev server, POST to
/api/agentwith a JSON body, and confirm the response. - Enable a trigger, send yourself a test email, and confirm your webhook fires.
Troubleshooting
Connection not found when calling tools. The user has not finished the OAuth flow, or you are passing a different userId than the one used in initiate. Composio is strict about identity — keep one canonical ID per user.
Model picks the wrong tool. Tighten your filter (fewer tools per call) or sharpen the system prompt. With more than 20 tools, accuracy drops sharply.
Rate limits from the underlying API. Composio surfaces the third-party error verbatim. Catch it in your agent runtime and either back off or surface it to the user.
Webhook signature failures. Verify the secret in Composio Settings matches the one your handler checks, and that your endpoint is reachable from the public internet (use ngrok in development).
Next Steps
- Add HubSpot or Salesforce to give your agent CRM superpowers
- Combine Composio with the Claude Agent SDK tutorial for a hybrid setup
- Wire Composio triggers into Inngest durable functions for retryable workflows
- Stand up Langfuse to trace every tool call end-to-end
- Replace shared OAuth apps with your own branded clients before shipping to real users
Conclusion
Composio collapses the months of glue work that separates a demo agent from a useful one. You wired authentication, picked tools intelligently, ran them through both the Vercel AI SDK and the OpenAI Agents SDK, reacted to real-time events, and shipped the whole thing behind a Next.js route — without writing a single integration by hand.
Your agent now talks to the apps your users actually live in. That is the unlock.