Skip to content

Composio Integration

@matimo/composio: Governed Access to Composio’s Tool Catalog

Section titled “@matimo/composio: Governed Access to Composio’s Tool Catalog”

@matimo/composio turns Composio’s 250+ integrations from “agent has raw API access” into “agent has governed, auditable, human-approvable API access”, same breadth, with Matimo’s policy engine, risk classification, and human-in-the-loop (HITL) approval layered on top.

TypeScript only. @matimo/composio ships for the TypeScript SDK (npm install @matimo/composio). There is no Python package for it yet.

Bring your own key. Every tool here requires your own COMPOSIO_API_KEY and Composio connected accounts, supplied at runtime, Matimo does not hold a Composio account or route requests through Matimo-operated infrastructure. You’re responsible for complying with Composio’s Terms of Service and Privacy Policy independently. Matimo is not affiliated with, endorsed by, or sponsored by Composio. See THIRD_PARTY_NOTICES.md for the full list of third-party providers Matimo can connect to.


Composio is a managed integration platform that provides pre-built connectors to hundreds of SaaS tools (Jira, Google Workspace, Microsoft 365, Salesforce, GitHub, Linear, Asana, and more). Its key primitives:

Concept What it is
Toolkit A named group of actions for one SaaS product (e.g. jira, googledrive)
Action / Slug A single API operation (e.g. JIRA_GET_ISSUE), addressable by slug
Connected Account An OAuth2 token for one user+toolkit combination (e.g. Sajesh’s Jira token)
Entity / User ID Your application’s stable identifier for a user (passed as composio_user_id)

@matimo/composio generates one Matimo tool per Composio action, exposes them all via MatimoInstance, and adds risk classification + optional HITL approval before any action reaches Composio’s execute endpoint.


An agent calls matimo.execute on a Composio tool. MatimoInstance runs the policy check, the optional onHITL callback and the HTTP executor against the Composio REST API, which forwards to your connected services such as Jira, Google Drive and Teams. An agent calls matimo.execute on a Composio tool. MatimoInstance runs the policy check, the optional onHITL callback and the HTTP executor against the Composio REST API, which forwards to your connected services such as Jira, Google Drive and Teams.

Every generated tool is type: http, no custom executor code. @matimo/composio stays in typescript/packages/composio/ as a managed third-party integration (intentionally separate from the root packages/ workspace).


Toolkit slug Matimo prefix Tools Examples
jira composio_jira_* 46 get_issue, create_issue, search_issues
asana composio_asana_* 84 get_task, create_task, update_task
linear composio_linear_* 21 get_issue, create_linear_issue, archive_issue
googledrive composio_googledrive_* 51 list_files, find_file, upload_file
googlecalendar composio_googlecalendar_* 28 list_calendars, create_event, find_event
outlook composio_outlook_* 43 list_emails, send_email, create_calendar
one_drive composio_one_drive_* 35 list_drives, download_file, search_items
share_point composio_share_point_* 6 create_folder, create_list, find_user
microsoft_teams composio_microsoft_teams_* 28 teams_list, teams_post_channel_message, create_meeting
gmail composio_gmail_* 23 send_email, fetch_emails, create_email_draft
googlesheets composio_googlesheets_* 36 batch_get, create_spreadsheet_row, create_chart
googledocs composio_googledocs_* 32 create_document, replace_all_text, insert_table_action
googleforms composio_googleforms_* 7 create_form, get_response, list_responses
googlemeet composio_googlemeet_* 9 create_meet, list_conference_records, get_transcripts_by_conference_record_id

Total: 449 tools across 14 toolkits (as of 0.3.0). Add more toolkits by running the generator, see Generating More Toolkits.


All 449 tools share the same three required inputs:

Input How it’s supplied
COMPOSIO_API_KEY Environment variable, auto-injected as x-api-key header
composio_user_id Tool parameter, your application’s stable user/tenant identifier
composio_connected_account_id Tool parameter, the OAuth connected account for this user + this toolkit

composio_connected_account_id is obtained when a user completes the Composio OAuth flow (“Connect Jira”, “Connect Google Drive”, etc.). Your application is responsible for storing and retrieving the correct account ID per user per toolkit, this is out of scope for @matimo/composio.

Every tool also takes an optional composio_toolkit_version (default latest, new in 0.3.0). It is sent to Composio as version. Without it Composio v3 runs the stale base version 00000000_00, which can call upstream APIs that no longer exist. Pass a dated version such as 20251027_00 to pin one.


import path from 'path';
import { MatimoInstance } from '@matimo/core';
const TOOLS_DIR = path.join(__dirname, 'path/to/packages/composio/tools');
const matimo = await MatimoInstance.init({ toolPaths: [TOOLS_DIR] });
// Low-risk: executes immediately
const user = await matimo.execute('composio_jira_get_current_user', {
composio_user_id: 'user_123',
composio_connected_account_id: 'ca_abc123',
});
// Medium- and high-risk tools also execute immediately with the default
// policy, see "Governance" below to require approval for them
const files = await matimo.execute('composio_googledrive_list_files', {
composio_user_id: 'user_123',
composio_connected_account_id: 'ca_def456',
});

Every generated tool has an explicit risk: low | medium | high field derived from the Composio action name:

Pattern in action slug Risk Examples
GET, LIST, FETCH, SEARCH, READ, FIND low JIRA_GET_ISSUE, GOOGLEDRIVE_LIST_FILES
CREATE, SEND, UPDATE, EDIT, ADD, UPLOAD, INVITE medium JIRA_CREATE_ISSUE, OUTLOOK_SEND_EMAIL
DELETE, REMOVE, ARCHIVE, REVOKE, CANCEL high JIRA_DELETE_ISSUE, GOOGLECALENDAR_CALENDARS_DELETE
Matches none medium (safe default) n/a

Destructive patterns are checked first, so ARCHIVE_AND_GET → high. Some actions are overridden manually in scripts/risk-overrides.json when the heuristic gets it wrong (e.g. GOOGLEDRIVE_EMPTY_TRASH → high, GOOGLECALENDAR_CLEAR_CALENDAR → high).

Read the declared level from the definition. classifyExecutionRisk(tool), which the policy engine uses, rates every Composio tool at least medium, because each one is an HTTP POST to Composio:

import { classifyExecutionRisk } from '@matimo/core';
const del = matimo.getTool('composio_jira_delete_issue');
console.log(del?.risk, classifyExecutionRisk(del!)); // 'high' 'high'
const get = matimo.getTool('composio_jira_get_current_user');
console.log(get?.risk, classifyExecutionRisk(get!)); // 'low' 'medium'

By default nothing asks. Generated Composio tools are HTTP POSTs and declare no requires_approval, so 0.2.0’s per-call approval default (HTTP DELETE and command tools) does not cover them: even a risk: high action such as composio_jira_delete_issue runs as soon as it is called. Turn on one of the two options below before giving an agent write access.

Quarantine high-risk actions with the default policy engine:

const matimo = await MatimoInstance.init({
toolPaths: [TOOLS_DIR],
policyConfig: { enableHITL: true, hitlMinRiskLevel: 'high' },
onHITL: async (request) => {
console.log(`Approval needed: ${request.toolName} (${request.riskLevel})`);
return promptUser(); // Promise<boolean>
},
});

This pauses the 45 risk: high Composio tools, and every other high or critical tool in the instance (native DELETE tools, execute, edit, …). A lower threshold cannot separate Composio reads from writes, since the engine rates every Composio POST at least medium.

Quarantine by declared risk with a small custom engine, to also pause medium writes while low reads run immediately:

import {
MatimoInstance,
DefaultPolicyEngine,
type PolicyEngine,
type PolicyContext,
type PolicyDecision,
type RiskLevel,
type ToolDefinition,
} from '@matimo/core';
const QUARANTINE_LEVELS: ReadonlySet<RiskLevel> = new Set(['medium', 'high']);
class ComposioRiskPolicy implements PolicyEngine {
private base = new DefaultPolicyEngine();
canExecute(ctx: PolicyContext, tool: ToolDefinition): PolicyDecision {
const base = this.base.canExecute(ctx, tool);
if (base.allowed !== true) return base;
if (tool.name.startsWith('composio_') && tool.risk && QUARANTINE_LEVELS.has(tool.risk)) {
return {
allowed: 'pending_approval',
riskLevel: tool.risk,
reason: `${tool.name} (risk: ${tool.risk}) requires approval`,
toolName: tool.name,
};
}
return { allowed: true };
}
canCreate(ctx: PolicyContext, tool: ToolDefinition): PolicyDecision {
return this.base.canCreate(ctx, tool);
}
filterForAgent(ctx: PolicyContext, tools: ToolDefinition[]): ToolDefinition[] {
return this.base.filterForAgent(ctx, tools);
}
}
const matimo = await MatimoInstance.init({
toolPaths: [TOOLS_DIR],
policy: new ComposioRiskPolicy(),
onHITL: async (request) => {
// Wire to: interactive prompt, Slack message, approval queue, etc.
console.log(`Approval needed: ${request.toolName} (${request.riskLevel})`);
return promptUser(); // Promise<boolean>
},
});

With no onHITL, a quarantined call is rejected. Approvals and rejections reach onEvent and the auditSink as tool:quarantine_approved / tool:quarantine_rejected.

See typescript/examples/tools/composio/composio-with-approval.ts for the full runnable example.


With 449 tools, loading all of them into a LangChain agent exceeds OpenAI’s 128-tool limit. Always filter to the toolkits relevant to the agent before binding:

import { MatimoInstance, convertToolsToLangChain, type ToolDefinition } from '@matimo/core';
import { ChatOpenAI } from '@langchain/openai';
import { createAgent } from 'langchain';
const matimo = await MatimoInstance.init({ toolPaths: [TOOLS_DIR] });
// Pick only the toolkits this agent needs
const jiraTools = matimo.getRegistry().getAll()
.filter(t => t.name.startsWith('composio_jira_'));
// convertToolsToLangChain(tools, matimoInstance, envVarsToInject)
// COMPOSIO_API_KEY is resolved from process.env automatically via Matimo's
// auth-parameter injection. composio_user_id and composio_connected_account_id
// are tool parameters, include them in the task/system prompt so the LLM
// knows what values to pass on each call.
const langchainTools = await convertToolsToLangChain(
jiraTools as ToolDefinition[],
matimo,
{ COMPOSIO_API_KEY: process.env.COMPOSIO_API_KEY! }
);
const agent = await createAgent({ model: new ChatOpenAI({ modelName: 'gpt-4o-mini' }), tools: langchainTools as any[] });
const response = await agent.invoke({
messages: [{ role: 'user', content: 'Find all unresolved issues assigned to me. composio_user_id="user_123", composio_connected_account_id="ca_abc123"' }],
});
const lastMessage = response.messages[response.messages.length - 1];
console.log(lastMessage.content);

Note on Jira search actions: As of March 2026, Jira removed the /rest/api/3/search endpoint. If composio_jira_search_issues or composio_jira_search_for_issues_using_jql_post return HTTP 410, Composio’s catalog hasn’t updated to the new /rest/api/3/search/jql endpoint yet. Use composio_jira_get_issue or composio_jira_get_current_user as a workaround, or wait for Composio to update the action.

See typescript/examples/tools/composio/composio-langchain.ts for the complete example.


Tool names follow composio_<toolkit_lower>_<action_slug_lower>. Some Composio action slugs already include the toolkit name, resulting in repetition:

Composio slug Matimo tool name
JIRA_GET_ISSUE composio_jira_get_issue
LINEAR_CREATE_LINEAR_ISSUE composio_linear_create_linear_issue
ONE_DRIVE_ONEDRIVE_CREATE_FOLDER composio_one_drive_onedrive_create_folder
SHARE_POINT_SHAREPOINT_CREATE_LIST composio_share_point_sharepoint_create_list

This is Composio’s own naming, Matimo preserves it exactly. Always verify tool names against the tools/composio_*/ directories rather than assuming a “natural” name.


The tool definitions under tools/composio_*/ are generated, do not hand-edit them. To add a new Composio toolkit:

Terminal window
cd typescript/
export COMPOSIO_API_KEY=your-key
# Add new toolkits (idempotent: existing files are left untouched)
pnpm generate:composio --toolkits=SALESFORCE,HUBSPOT,GITHUB
# Re-generate a toolkit after editing risk-overrides.json
pnpm generate:composio --toolkits=SALESFORCE --force-refresh
# Validate all generated YAML against Matimo's schema
pnpm validate-tools

To override a misclassified risk level, add an entry to typescript/packages/composio/scripts/risk-overrides.json:

{
"GOOGLEDRIVE_EMPTY_TRASH": "high",
"GOOGLECALENDAR_CLEAR_CALENDAR": "high",
"SALESFORCE_BULK_DELETE_RECORDS": "high"
}

Then re-run with --force-refresh for the affected toolkits.


Every composio tool returns the same envelope:

{
"success": true,
"statusCode": 200,
"data": {
"data": { "...action-specific payload..." },
"error": null,
"successful": true
}
}

Check data.successful (not just success) to confirm the underlying action succeeded. A success: true with data.successful: false means Composio’s HTTP layer worked but the integration itself rejected the call (e.g. invalid issue ID, expired token, insufficient scope on the connected account).


If composio_connected_account_id is missing, invalid, or not yet connected, Composio returns a “no connected account” error. Surface this to the user as “connect <toolkit> first”, do not retry, as retrying cannot create the missing OAuth connection.