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/composioships 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_KEYand 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. SeeTHIRD_PARTY_NOTICES.mdfor the full list of third-party providers Matimo can connect to.
What Is Composio?
Section titled “What Is Composio?”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.
Architecture
Section titled “Architecture”
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).
Available Toolkits
Section titled “Available Toolkits”| 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.
Every Tool Call Needs Three Things
Section titled “Every Tool Call Needs Three Things”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.
Quick Start
Section titled “Quick Start”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 immediatelyconst 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 themconst files = await matimo.execute('composio_googledrive_list_files', { composio_user_id: 'user_123', composio_connected_account_id: 'ca_def456',});Risk Classification
Section titled “Risk Classification”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'Governance: Adding HITL Approval
Section titled “Governance: Adding HITL Approval”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.
LangChain Integration
Section titled “LangChain Integration”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 needsconst 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/searchendpoint. Ifcomposio_jira_search_issuesorcomposio_jira_search_for_issues_using_jql_postreturn HTTP 410, Composio’s catalog hasn’t updated to the new/rest/api/3/search/jqlendpoint yet. Usecomposio_jira_get_issueorcomposio_jira_get_current_useras a workaround, or wait for Composio to update the action.
See typescript/examples/tools/composio/composio-langchain.ts for the complete example.
Naming Conventions
Section titled “Naming Conventions”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.
Generating More Toolkits
Section titled “Generating More Toolkits”The tool definitions under tools/composio_*/ are generated, do not hand-edit them. To add a new Composio toolkit:
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.jsonpnpm generate:composio --toolkits=SALESFORCE --force-refresh
# Validate all generated YAML against Matimo's schemapnpm validate-toolsTo 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.
Response Structure
Section titled “Response Structure”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).
Missing Connected Account
Section titled “Missing 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.