ts/docs/api/tool-router.md
The Tool Router provides a powerful way to create isolated MCP (Model Context Protocol) sessions for users with scoped access to toolkits and tools. It enables dynamic configuration of which tools are available within a session and manages authentication for multiple toolkits.
Tool Router allows you to:
Note: When using MCP clients to connect to Tool Router, you don't need to pass a provider to the Composio constructor. A provider is only required when using
session.tools()to get framework-specific tool objects.
When using Tool Router with MCP clients, you don't need to pass a provider:
# npm
npm install @composio/[email protected]
# pnpm
pnpm add @composio/[email protected]
# yarn
yarn add @composio/[email protected]
import { Composio } from '@composio/core';
const composio = new Composio();
// Create a session for a user with access to Gmail tools
const session = await composio.create('user_123', {
toolkits: ['gmail']
});
// Use the MCP URL with any MCP-compatible client
console.log(session.mcp.url);
If you want to use session.tools() to get tools formatted for a specific AI framework, pass the appropriate provider:
import { Composio } from '@composio/core';
import { VercelProvider } from '@composio/vercel';
const composio = new Composio({
provider: new VercelProvider(),
});
// Create a session for a user with access to Gmail tools
const session = await composio.create('user_123', {
toolkits: ['gmail']
});
// Get the tools formatted for Vercel AI SDK
const tools = await session.tools();
Use composio.create() to create a new Tool Router session:
import { Composio } from '@composio/core';
const composio = new Composio();
// Create a session with Gmail access
const session = await composio.create('user_123', {
toolkits: ['gmail']
});
console.log('Session ID:', session.sessionId);
console.log('MCP URL:', session.mcp.url);
If you have an existing session ID, you can retrieve it using composio.use():
const session = await composio.use('existing_session_id');
// Access session properties
console.log(session.sessionId);
console.log(session.mcp.url);
The session creation accepts a configuration object with the following options:
toolkitsSpecify which toolkits to enable or disable in the session.
// Simple array of toolkit slugs to enable
const session = await composio.create('user_123', {
toolkits: ['gmail', 'slack', 'github']
});
// Explicit enabled configuration
const session = await composio.create('user_123', {
toolkits: { enable: ['gmail', 'slack'] }
});
// Disable specific toolkits (enable all others)
const session = await composio.create('user_123', {
toolkits: { disable: ['calendar'] }
});
toolsFine-grained control over which tools are available within toolkits. Configure tools per toolkit using the toolkit slug as the key.
const session = await composio.create('user_123', {
toolkits: ['gmail', 'slack'],
tools: {
// Configure tools per toolkit
gmail: ['gmail_fetch_emails', 'gmail_send_email'], // Only these tools
// OR use enable/disable objects
// gmail: { enable: ['gmail_fetch_emails'] }
// gmail: { disable: ['gmail_delete_email'] }
// You can also set tags per toolkit to override global tags
slack: { tags: ['readOnlyHint'] }
}
});
tagsGlobal tags to filter tools by their behavior hints. Tags can be overridden per toolkit in the tools configuration.
const session = await composio.create('user_123', {
toolkits: ['gmail', 'github'],
// Global tags applied to all toolkits
tags: ['readOnlyHint', 'idempotentHint']
});
Available tags:
readOnlyHint - Tools that only read datadestructiveHint - Tools that modify or delete dataidempotentHint - Tools that can be safely retriedopenWorldHint - Tools that operate in an open world contextauthConfigsMap toolkits to specific authentication configurations:
const session = await composio.create('user_123', {
toolkits: ['gmail', 'github'],
authConfigs: {
gmail: 'ac_gmail_work',
github: 'ac_github_personal'
}
});
connectedAccountsMap toolkits to specific connected account IDs:
const session = await composio.create('user_123', {
toolkits: ['gmail'],
connectedAccounts: {
gmail: 'ca_abc123'
}
});
manageConnectionsControl how connections are managed within the session:
// Boolean: enable/disable automatic connection management
const session = await composio.create('user_123', {
toolkits: ['gmail'],
manageConnections: true // default
});
// Object: fine-grained control
const session = await composio.create('user_123', {
toolkits: ['gmail'],
manageConnections: {
enable: true,
callbackUrl: 'https://your-app.com/auth/callback',
waitForConnections: true // Wait for user to complete authentication before proceeding
}
});
The waitForConnections property (new in v0.4.0) allows the tool router session to wait for users to complete authentication before proceeding to the next step. When set to true, the session will block execution until all required connections are established.
const session = await composio.create('user_123', {
toolkits: ['gmail', 'slack'],
manageConnections: {
enable: true,
callbackUrl: 'https://your-app.com/auth/callback',
waitForConnections: true // Session waits for connections to complete
}
});
workbenchConfigure workbench behavior for tool execution:
const session = await composio.create('user_123', {
toolkits: ['gmail'],
workbench: {
enableProxyExecution: true,
autoOffloadThreshold: 400000 // Automatically offload to workbench if response > 400k characters
}
});
experimentalConfigure experimental features for the session. These features are not stable and may be modified or removed in future versions.
const session = await composio.create('user_123', {
toolkits: ['gmail'],
experimental: {
assistivePrompt: {
userTimezone: 'America/New_York' // IANA timezone identifier for timezone-aware assistive prompts
}
}
});
// Access the generated assistive prompt from the session response
if (session.experimental?.assistivePrompt) {
console.log('Assistive prompt:', session.experimental.assistivePrompt);
}
The userTimezone field accepts an IANA timezone identifier (e.g., 'America/New_York', 'Europe/London') which is used to generate a timezone-aware system prompt for optimal tool router usage.
Define your own tools that run in-process alongside Composio's remote tools. Custom tools are automatically indexed for search and routed to your execute function when called.
There are three ways to define custom tools:
1. Standalone tool — no auth needed, passed in customTools:
import { experimental_createTool } from '@composio/core';
import { z } from 'zod/v3';
const grep = experimental_createTool('GREP', {
name: 'Grep Search',
description: 'Search for patterns in files',
inputParams: z.object({ pattern: z.string(), path: z.string() }),
execute: async (input) => ({ matches: [] }),
});
2. Extension tool — extends a Composio toolkit, inherits its auth. Passed in customTools with extendsToolkit:
const getImportant = experimental_createTool('GET_IMPORTANT_EMAILS', {
name: 'Get Important Emails',
description: 'Fetch high-priority emails',
extendsToolkit: 'gmail',
inputParams: z.object({ limit: z.number().default(10) }),
execute: async (input, ctx) => {
const result = await ctx.execute('GMAIL_SEARCH', { query: 'is:important' });
return { emails: result.data };
},
});
3. Custom toolkit — groups related no-auth tools. Passed in customToolkits:
import { experimental_createToolkit } from '@composio/core';
const sed = experimental_createTool('SED', {
name: 'Sed Replace',
description: 'Find and replace in files',
inputParams: z.object({ pattern: z.string(), replacement: z.string() }),
execute: async (input) => ({ replaced: 0 }),
});
const devTools = experimental_createToolkit('DEV_TOOLS', {
name: 'Dev Tools',
description: 'Local dev utilities',
tools: [sed],
});
Creating a session with custom tools:
const session = await composio.create('user_123', {
toolkits: ['gmail'],
experimental: {
customTools: [grep, getImportant],
customToolkits: [devTools],
},
});
Executing custom tools — use the same slug you defined:
// Standalone tool
await session.execute('GREP', { pattern: 'TODO', path: '/src' });
// Extension tool
await session.execute('GET_IMPORTANT_EMAILS', { limit: 5 });
// Toolkit tool
await session.execute('SED', { pattern: 'foo', replacement: 'bar' });
Custom tools are searched alongside Composio tools. When the LLM calls a custom tool, the SDK executes it in-process — remote tools in the same batch are sent to the backend in parallel.
A Tool Router session provides the following properties and methods:
sessionIdThe unique identifier for this session.
console.log(session.sessionId);
mcpThe MCP server configuration for this session, including authentication headers.
console.log(session.mcp.url); // The URL to connect to
console.log(session.mcp.type); // 'http' or 'sse'
console.log(session.mcp.headers); // Authentication headers (includes x-api-key if configured)
tools()Retrieve the tools available in the session, formatted for your AI framework.
const tools = await session.tools();
// With session-specific modifiers (new in v0.4.0)
const tools = await session.tools({
modifySchema: ({ toolSlug, toolkitSlug, schema }) => {
// Customize the tool schema
return schema;
},
beforeExecute: ({ toolSlug, toolkitSlug, sessionId, params }) => {
console.log(`Executing ${toolSlug} in session ${sessionId}`);
return params;
},
afterExecute: ({ toolSlug, toolkitSlug, sessionId, result }) => {
console.log(`Completed ${toolSlug} in session ${sessionId}`);
return result;
}
});
Tool Router sessions now support enhanced modifier types (introduced in v0.4.0) that include session context:
modifySchema: Customize tool schemas before they're sent to the AI modelbeforeExecute: Modify parameters before execution, with access to session IDafterExecute: Transform results after execution, with access to session IDThese modifiers provide better context for session-based tool execution, allowing you to track which session is executing which tools.
Tool Router provides meta tools for managing connections and session state. You can access these directly using the getRawToolRouterMetaTools method (introduced in v0.4.0):
import { Composio } from '@composio/core';
const composio = new Composio();
// Get raw meta tools for a session
const metaTools = await composio.tools.getRawToolRouterMetaTools('session_123', {
modifySchema: ({ toolSlug, toolkitSlug, schema }) => {
// Customize meta tool schemas
console.log(`Modifying schema for ${toolSlug}`);
return schema;
}
});
console.log('Available meta tools:', metaTools.map(t => t.name));
Meta tools allow you to:
This method is useful when you need direct access to the underlying meta tools without creating a full session object.
execute()Execute a tool within the session. Custom tools run in-process; remote tools are sent to the Composio backend. Works with any tool — custom or remote — using the same API.
// Custom tool
const result = await session.execute('GET_USER_CONTEXT', { category: 'prefs' });
console.log(result.data); // { preferences: { ... } }
console.log(result.error); // null on success
// Remote Composio tool — same API
const weather = await session.execute('WEATHERMAP_WEATHER', { location: 'Tokyo' });
search()Search for tools by semantic use case. Returns relevant tools with schemas and guidance.
const results = await session.search({ query: 'send an email via gmail' });
for (const result of results.results) {
console.log(result.useCase);
console.log(result.primaryToolSlugs); // e.g. ['GMAIL_SEND_EMAIL']
}
proxyExecute()Proxy an API call through Composio's auth layer using the session's connected account. The backend resolves the connected account from the toolkit within the session.
const result = await session.proxyExecute({
toolkit: 'github',
endpoint: 'https://api.github.com/user',
method: 'GET',
parameters: [
{ in: 'header', name: 'X-Custom', value: 'value' },
],
});
authorize()Initiate an authorization flow for a toolkit.
// Start authorization for Gmail
const connectionRequest = await session.authorize('gmail');
console.log(connectionRequest.redirectUrl); // URL to redirect user for auth
// Wait for the user to complete authorization
const connectedAccount = await connectionRequest.waitForConnection();
console.log('Connected:', connectedAccount);
toolkits()Query the connection state of toolkits in the session.
const { items, nextCursor, totalPages } = await session.toolkits();
for (const toolkit of items) {
console.log(`${toolkit.name} (${toolkit.slug})`);
console.log(` Connected: ${toolkit.connection?.isActive}`);
if (toolkit.connection?.connectedAccount) {
console.log(` Account ID: ${toolkit.connection.connectedAccount.id}`);
console.log(` Status: ${toolkit.connection.connectedAccount.status}`);
}
}
// Pagination support
const moreToolkits = await session.toolkits({
nextCursor: nextCursor,
limit: 10
});
// Filter by specific toolkits
const filteredToolkits = await session.toolkits({
toolkits: ['gmail', 'slack']
});
// Combine filtering with pagination
const paginatedFilteredToolkits = await session.toolkits({
toolkits: ['gmail', 'github'],
limit: 5,
nextCursor: 'cursor_abc'
});
filesEach session has a virtual filesystem mount for storing and retrieving files. Use session.experimental.files to list, upload, download, and delete files. This is useful for agent workflows, document processing, and tools that need file access.
// Upload a file (from path, URL, File, or buffer)
const file = await session.experimental.files.upload('/path/to/report.pdf');
const file = await session.experimental.files.upload(buffer, { remotePath: 'data.json' });
// List files with pagination
const { items, nextCursor } = await session.experimental.files.list({ path: '/' });
// Download a file
const remoteFile = await session.experimental.files.download('report.pdf');
const buffer = await remoteFile.buffer();
await remoteFile.save('/tmp/report.pdf');
// Delete a file
await session.experimental.files.delete('/temp/cache.json');
See Tool Router Session Files for full documentation, including upload input types (paths, URLs, File, buffers), mimetype detection, pagination, and the RemoteFile API.
When using the session.tools() method, you need to pass a provider to get framework-specific tools:
import { openai } from '@ai-sdk/openai';
import { Composio } from '@composio/core';
import { VercelProvider } from '@composio/vercel';
import { stepCountIs, streamText } from 'ai';
const composio = new Composio({
provider: new VercelProvider(),
});
const session = await composio.create('user_123', {
toolkits: ['gmail']
});
const tools = await session.tools();
const stream = await streamText({
model: openai('gpt-4o-mini'),
prompt: 'Find my last email from gmail?',
stopWhen: stepCountIs(10),
tools,
});
for await (const textPart of stream.textStream) {
process.stdout.write(textPart);
}
When using MCP clients, no provider is needed. The MCP client fetches tools directly from the MCP server URL:
import { openai } from '@ai-sdk/openai';
import { experimental_createMCPClient as createMCPClient } from '@ai-sdk/mcp';
import { Composio } from '@composio/core';
import { stepCountIs, streamText } from 'ai';
// No provider needed when using MCP
const composio = new Composio();
const { mcp } = await composio.create('user_123', {
toolkits: ['gmail'],
manageConnections: true,
tools: {
gmail: {
disable: ['gmail_send_email'], // Disable specific tools
}
}
});
// Create MCP client using the session URL and headers
const client = await createMCPClient({
transport: {
type: 'http',
url: mcp.url,
headers: mcp.headers // Uses pre-configured authentication headers
}
});
const tools = await client.tools();
const stream = await streamText({
model: openai('gpt-4o-mini'),
prompt: 'Find my last email from gmail?',
stopWhen: stepCountIs(10),
onStepFinish: (step) => {
if (step.toolCalls.length > 0) {
for (const toolCall of step.toolCalls) {
console.log(`Executed ${toolCall.toolName}`);
}
}
},
tools,
});
for await (const textPart of stream.textStream) {
process.stdout.write(textPart);
}
LangChain can connect to Tool Router via MCP adapters. No provider is needed:
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent } from "langchain";
import { Composio } from "@composio/core";
// No provider needed when using MCP
const composio = new Composio();
const llm = new ChatOpenAI({
model: "gpt-4o",
});
const session = await composio.create('user_123', {
toolkits: ['gmail']
});
const client = new MultiServerMCPClient({
composio: {
transport: "http",
url: session.mcp.url,
headers: session.mcp.headers // Uses pre-configured authentication headers
},
});
const tools = await client.getTools();
const agent = createAgent({
name: "Gmail Assistant",
systemPrompt: "You are a helpful gmail assistant.",
model: llm,
tools,
});
const result = await agent.invoke({
messages: [{ role: "user", content: "Fetch my last email from gmail" }],
});
console.log(result);
OpenAI Agents SDK supports hosted MCP tools. No provider is needed:
import { Agent, run, hostedMcpTool } from '@openai/agents';
import { Composio } from '@composio/core';
// No provider needed when using MCP
const composio = new Composio();
const session = await composio.create('user_123', {
toolkits: ['gmail']
});
console.log(`Tool Router Session Created: ${session.sessionId}`);
console.log(`Connecting to MCP server: ${session.mcp.url}`);
const mcpTool = hostedMcpTool({
serverLabel: 'ComposioApps',
serverUrl: session.mcp.url,
headers: session.mcp.headers // Uses pre-configured authentication headers
});
const agent = new Agent({
name: 'Gmail Assistant',
instructions: 'You are a helpful gmail assistant.',
tools: [mcpTool],
});
const result = await run(agent, 'Summarize my last email from gmail', {
stream: true,
});
const stream = result.toStream();
for await (const event of stream) {
if (event.type === 'raw_model_stream_event' && event.data.delta) {
process.stdout.write(event.data.delta);
}
}
Claude Agents SDK supports MCP servers natively. No provider is needed:
import { query } from "@anthropic-ai/claude-agent-sdk";
import { Composio } from '@composio/core';
// No provider needed when using MCP
const composio = new Composio();
const session = await composio.create('user_123', {
toolkits: ['gmail']
});
const stream = await query({
prompt: 'Use composio tools to fetch my last email from gmail',
options: {
model: 'claude-sonnet-4-5-20250929',
permissionMode: "bypassPermissions",
mcpServers: {
composio: {
type: 'http',
url: session.mcp.url,
headers: session.mcp.headers // Uses pre-configured authentication headers
}
},
}
});
for await (const event of stream) {
if (event.type === "result" && event.subtype === "success") {
process.stdout.write(event.result);
}
}
When a user needs to connect a toolkit, use the authorize() method:
import { Composio } from '@composio/core';
const composio = new Composio();
const session = await composio.create('user_123', {
toolkits: ['gmail']
});
// Initiate authorization for Gmail
const connectionRequest = await session.authorize("gmail");
// Log the redirect URL for the user
console.log('Redirect URL:', connectionRequest.redirectUrl);
// Wait for the user to complete the authorization
const connectedAccount = await connectionRequest.waitForConnection();
console.log('Connected Account:', connectedAccount);
You can also provide a custom callback URL:
const connectionRequest = await session.authorize("gmail", {
callbackUrl: 'https://your-app.com/auth/callback'
});
Use the toolkits() method to check connection states:
import { Composio } from '@composio/core';
const composio = new Composio();
const session = await composio.create('user_123');
// Get all toolkits
const toolkits = await session.toolkits();
console.log(JSON.stringify({ toolkits }, null, 2));
// Filter by specific toolkits
const gmailAndSlack = await session.toolkits({
toolkits: ['gmail', 'slack']
});
The response includes:
{
items: [
{
slug: 'gmail',
name: 'Gmail',
logo: 'https://...',
isNoAuth: false,
connection: {
isActive: true,
authConfig: {
id: 'ac_xxx',
mode: 'OAUTH2',
isComposioManaged: true
},
connectedAccount: {
id: 'ca_xxx',
status: 'ACTIVE'
}
}
}
],
nextCursor: 'cursor_abc',
totalPages: 1
}
Add custom behavior before and after tool execution. Tool Router supports enhanced session-specific modifiers (v0.4.0+):
import { SessionExecuteMetaModifiers } from "@composio/core";
const modifiers: SessionExecuteMetaModifiers = {
modifySchema: ({ toolSlug, toolkitSlug, schema }) => {
// Customize tool schema
console.log(`Modifying schema for ${toolSlug}`);
return schema;
},
beforeExecute: ({ toolSlug, toolkitSlug, sessionId, params }) => {
console.log(`[Session: ${sessionId}] Executing ${toolSlug} from ${toolkitSlug}`);
// Add custom logging, validation, or parameter transformation
return params;
},
afterExecute: ({ toolSlug, toolkitSlug, sessionId, result }) => {
console.log(`[Session: ${sessionId}] Executed ${toolSlug} from ${toolkitSlug}`);
// Transform results, add telemetry, or handle errors
return result;
},
};
const tools = await session.tools(modifiers);
Tool Router provides specialized modifier types for session-based execution:
SessionExecuteMetaModifiers: Complete set of modifiers with session contextSessionMetaToolOptions: Configuration options for meta toolsThese types include the sessionId in their parameters, allowing you to track and manage tool execution across different user sessions.
User Isolation: Create separate sessions per user to ensure proper isolation of connections and tools.
Toolkit Selection: Only enable toolkits that are necessary for the use case to maintain security and reduce complexity.
Connection Management: Use manageConnections: true (default) for interactive applications where users can be prompted to connect accounts.
Tag Filtering: Use global tags to filter tools by their behavior (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) and override per toolkit when needed.
Session Reuse: Store and reuse sessionId to maintain user sessions across requests.
// Store session ID after creation
const session = await composio.create('user_123', config);
await redis.set(`session:user_123`, session.sessionId);
// Retrieve existing session
const sessionId = await redis.get(`session:user_123`);
if (sessionId) {
const session = await composio.use(sessionId);
}
interface ToolRouterCreateSessionConfig {
toolkits?: string[] | { enable: string[] } | { disable: string[] };
tools?: Record<string,
string[] |
{ enable: string[] } |
{ disable: string[] } |
{ tags: ('readOnlyHint' | 'destructiveHint' | 'idempotentHint' | 'openWorldHint')[] }
>;
tags?: ('readOnlyHint' | 'destructiveHint' | 'idempotentHint' | 'openWorldHint')[];
authConfigs?: Record<string, string>;
connectedAccounts?: Record<string, string>;
manageConnections?: boolean | {
enable?: boolean;
callbackUrl?: string;
waitForConnections?: boolean; // NEW in v0.4.0: Wait for connections to complete
};
workbench?: {
enableProxyExecution?: boolean;
autoOffloadThreshold?: number;
};
experimental?: {
assistivePrompt?: {
userTimezone?: string; // IANA timezone identifier for timezone-aware assistive prompts
};
customTools?: CustomTool[]; // Standalone or extension tools (from experimental_createTool)
customToolkits?: CustomToolkit[]; // Grouped no-auth tools (from experimental_createToolkit)
};
}
interface ToolRouterSession {
sessionId: string;
mcp: {
type: 'http' | 'sse';
url: string;
headers?: Record<string, string>; // Authentication headers (includes x-api-key if configured)
};
tools: (modifiers?: ProviderOptions) => Promise<Tools>;
execute: (toolSlug: string, arguments?: Record<string, unknown>) => Promise<ToolRouterSessionExecuteResponse>;
search: (params: { query: string; toolkits?: string[] }) => Promise<ToolRouterSessionSearchResponse>;
proxyExecute: (params: SessionProxyExecuteParams) => Promise<ToolRouterSessionExecuteResponse>;
authorize: (toolkit: string, options?: { callbackUrl?: string }) => Promise<ConnectionRequest>;
toolkits: (options?: {
toolkits?: string[]; // Filter by specific toolkit slugs
nextCursor?: string; // Pagination cursor
limit?: number; // Number of items per page
}) => Promise<ToolkitConnectionsDetails>;
files: ToolRouterSessionFilesMount; // List, upload, download, delete files. See tool-router-files.md
experimental?: {
assistivePrompt?: string; // Generated system prompt for optimal tool router usage
};
}
interface ToolkitConnectionState {
slug: string;
name: string;
logo?: string;
isNoAuth: boolean;
connection: {
isActive: boolean;
authConfig?: {
id: string;
mode: string;
isComposioManaged: boolean;
};
connectedAccount?: {
id: string;
status: string;
};
};
}
interface SessionExecuteMetaModifiers {
modifySchema?: (context: {
toolSlug: string;
toolkitSlug: string;
schema: any;
}) => any;
beforeExecute?: (context: {
toolSlug: string;
toolkitSlug: string;
sessionId: string;
params: any;
}) => any;
afterExecute?: (context: {
toolSlug: string;
toolkitSlug: string;
sessionId: string;
result: any;
}) => any;
}
The new waitForConnections property allows sessions to wait for users to complete authentication before proceeding:
const session = await composio.create('user_123', {
toolkits: ['gmail', 'slack'],
manageConnections: {
enable: true,
callbackUrl: 'https://your-app.com/callback',
waitForConnections: true // NEW
}
});
Session-specific modifiers now include session context, making it easier to track and manage tool execution:
const tools = await session.tools({
beforeExecute: ({ toolSlug, toolkitSlug, sessionId, params }) => {
console.log(`[${sessionId}] Executing ${toolSlug}`);
return params;
}
});
New method to fetch meta tools directly from a session:
const metaTools = await composio.tools.getRawToolRouterMetaTools('session_123', {
modifySchema: ({ toolSlug, schema }) => schema
});
All changes in v0.4.0 are fully backward compatible with existing code.