@matimo/composio
v0.1.5
Published
Governed access to Composio's tool catalog for Matimo — policy engine, risk classification, and HITL approval on top of Composio's 250+ integrations
Readme
@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.
Every tool in this package proxies a single Composio action via Composio's REST execute endpoint:
POST https://backend.composio.dev/api/v3/tools/execute/{TOOL_SLUG}📦 Installation
npm install @matimo/composio
# or
pnpm add @matimo/composio🧬 Tools Are Generated, Not Hand-Written
Unlike other Matimo provider packages, the tools/composio_*/ directory is
generated by scripts/generate-tools.ts
from Composio's live catalog API. Do not hand-edit
tools/composio_<toolkit>_<action>/definition.yaml files — changes will be
overwritten the next time the generator runs.
Running the Generator
# Requires COMPOSIO_API_KEY in the environment (used only at generation time,
# to call Composio's catalog API)
export COMPOSIO_API_KEY="your-composio-project-api-key"
pnpm generate:composio --toolkits=JIRA,LINEAR,ASANA- Idempotent and incremental: re-running with new toolkits only adds new files; existing tool definitions are left untouched.
- Pass
--force-refreshto regenerate every file for the named toolkits (e.g. after editingscripts/risk-overrides.json):
pnpm generate:composio --toolkits=JIRA --force-refreshWhat Gets Generated
For each Composio action (e.g. JIRA_GET_ISSUE), the generator writes
tools/composio_jira_get_issue/definition.yaml:
name: composio_jira_get_issue
description: Fetch a Jira issue by ID
version: '1.0.0'
risk: low
parameters:
composio_user_id:
type: string
required: true
description: The Composio entity/user ID for the calling tenant or user.
composio_connected_account_id:
type: string
required: true
description: >-
The Composio connected account ID for the JIRA toolkit, scoped to the
calling tenant. The "Connect JIRA" flow must be completed before this
tool will succeed.
issue_id_or_key:
type: string
required: true
description: The ID or key of the Jira issue to retrieve.
execution:
type: http
method: POST
url: https://backend.composio.dev/api/v3/tools/execute/JIRA_GET_ISSUE
headers:
x-api-key: '{COMPOSIO_API_KEY}'
Content-Type: application/json
body:
user_id: '{composio_user_id}'
connected_account_id: '{composio_connected_account_id}'
arguments:
_matimo_tool: composio_jira_get_issue
issue_id_or_key: '{issue_id_or_key}'
timeout: 30000
authentication:
type: api_key
location: header
name: x-api-key
tags: [composio, jira]arguments._matimo_tool is a literal marker baked in at generation time
(not a {param} placeholder) — see
Why _matimo_tool Is in Every arguments Block
below.
Why _matimo_tool Is in Every arguments Block
Composio's /tools/execute/{slug} endpoint requires the arguments key to
be present in the request body, even when empty ({}) — omitting it
entirely returns HTTP 400 "Only one of 'text' or 'arguments' must be
provided". Matimo's HTTP executor, however, drops empty nested objects from
the request body after templating. For any action where the caller supplies
none of the optional parameters (or the action has no input parameters at
all), arguments would template down to {} and then be stripped entirely
— triggering that 400 on every such call.
The generator works around this by always including a literal
_matimo_tool: <tool name> entry in arguments. Because it's a plain string
baked in at generation time (not a {param} placeholder), it always survives
templating, keeping arguments non-empty. Composio tolerates the extra field
and ignores it; it also serves as a debugging breadcrumb in Composio's logs
identifying which Matimo tool made the call.
🔑 Runtime Configuration
Every generated tool requires three values, all supplied by the application
embedding Matimo (e.g. via its existing credential-injection layer — the same
mechanism that provides SLACK_BOT_TOKEN for @matimo/slack):
| Value | Type | How it's resolved |
|-------|------|--------------------|
| COMPOSIO_API_KEY | Environment variable | Auto-injected as the x-api-key header by Matimo's auth-parameter resolution (any execution-config placeholder containing key resolves from process.env) |
| composio_user_id | Tool parameter, required | The Composio entity/user ID for the calling tenant/user |
| composio_connected_account_id | Tool parameter, required | The connected account ID for the specific toolkit, scoped to the calling tenant — requires the "Connect <toolkit>" flow to have been completed |
How composio_user_id / composio_connected_account_id are obtained, stored,
or surfaced via a "Connect via Composio" UI is out of scope for this
package — see definition.yaml for details.
🚦 Risk Classification
Every generated tool sets an explicit risk: low | medium | high field. The
generator derives this from the Composio action's slug:
| Pattern in action slug | Risk | Examples |
|-------------------------|------|----------|
| GET, LIST, FETCH, SEARCH, READ, FIND | low | JIRA_GET_ISSUE, LINEAR_LIST_ISSUES |
| CREATE, SEND, UPDATE, EDIT, ADD, POST, UPLOAD, INVITE | medium | LINEAR_CREATE_ISSUE, GMAIL_SEND_EMAIL |
| DELETE, REMOVE, ARCHIVE, REVOKE, CANCEL | high | JIRA_DELETE_ISSUE, LINEAR_ARCHIVE_ISSUE |
| Matches none of the above | medium (default) | An unnecessary approval prompt beats a silent destructive write |
Destructive patterns are checked first, so an ambiguous name that also looks
destructive (e.g. ARCHIVE_AND_GET_ISSUE) is still flagged high.
Overriding the Heuristic
If the generator misclassifies a specific action, add an entry to
scripts/risk-overrides.json keyed by the
Composio tool slug:
{
"JIRA_DELETE_ISSUE_LINK": "high"
}Then re-run the generator with --force-refresh for the affected toolkit(s)
to apply the override.
Acting on risk: — This Is the Embedding Application's Job
classifyRisk(tool) always returns each generated tool's explicit risk:
field, but DefaultPolicyEngine.canExecute() does not itself gate
execute() calls on risk: — it only checks deprecation/draft status and
requires_approval + role. So with the default policy engine, a
risk: medium or risk: high composio tool executes immediately.
To get pending_approval quarantine for medium/high-risk composio tools
(e.g. composio_jira_create_issue, composio_jira_delete_issue), the
embedding application must do one of:
- Implement a custom
PolicyEnginewhosecanExecute()checksclassifyRisk(tool)against itsquarantineRiskLevelsand returns{ allowed: 'pending_approval', riskLevel, reason, toolName }—MatimoInstance.execute()will then invoke the configuredonHITLcallback before proceeding. - Register composio tools under
untrustedPathswithrequires_approval: trueand callreloadTools()with{ environment: 'prod' }—DefaultPolicyEngine.canCreate()returnspending_approvalfor medium/high-risk tools whenenableHITL: trueand the risk level is inquarantineRiskLevels.
See skills/composio/SKILL.md for how agents
should handle a pending_approval result if the host wires this up.
🚀 Quick Start
import { MatimoInstance } from 'matimo';
const matimo = await MatimoInstance.init({ autoDiscover: true });
// Low-risk: executes immediately
const result = await matimo.execute('composio_jira_get_issue', {
composio_user_id: 'user_123',
composio_connected_account_id: 'ca_abc123',
issue_id_or_key: 'PROJ-123',
});
// Medium-risk: executes immediately under DefaultPolicyEngine — see
// "Acting on risk:" above for how to make this return `pending_approval`
const created = await matimo.execute('composio_linear_create_linear_issue', {
composio_user_id: 'user_123',
composio_connected_account_id: 'ca_def456',
title: 'Investigate flaky test',
team_id: 'team_abc789',
});🧪 Testing Strategy (Exception to Standard Matimo Rules)
Most Matimo provider packages require 100% unit coverage, 4 TypeScript examples, and 3 Python examples per tool. That rule does not apply here: the initial toolkit set generates 200–1000+ tool definition files across 15–20 toolkits, and per-tool hand-written tests/examples for every generated file would be unmaintainable busywork that breaks on every catalog change.
Instead:
test/unit/generator.test.tscovers the generator itself —pnpm jest packages/composio— with mocked Composio API responses. It asserts:- Risk classification for representative action-name patterns (one case
per heuristic bucket: GET-like →
low, CREATE-like →medium, DELETE-like →high, ambiguous →mediumdefault, and destructive patterns winning over ambiguous matches) - Risk override resolution (
scripts/risk-overrides.jsonwins over the heuristic) - Parameter mapping from Composio's JSON Schema
input_parametersto Matimo'sparametersblock - Generated tool definitions are schema-valid via
validateToolDefinition - Pagination via
next_cursorwhen fetching a toolkit's catalog - Idempotent file writes (
created/skipped/forceRefresh)
- Risk classification for representative action-name patterns (one case
per heuristic bucket: GET-like →
pnpm validate-toolspassing on all generated YAML is the acceptance gate for generated files. No per-tool hand-written tests or examples (TypeScript or Python) are required or expected fortools/composio_*/.- There is no
executor.test.ts— every generated tool istype: http, so there is no shared executor to test.
🤝 Contributing
See CONTRIBUTING.md for guidelines on contributing to Matimo.
Part of the Matimo ecosystem — Write YAML once, use everywhere.
