Skip to content

Adding Tools to Matimo

Tools in Matimo are organized as independent npm packages in the packages/ directory. This guide walks you through creating and publishing a new tool provider.

Each tool provider:

  • Is published to npm as @matimo/{provider} (e.g., @matimo/slack, @matimo/github)
  • Can be installed via CLI: matimo install {provider}
  • Is auto-discovered when installed in node_modules/@matimo/*
  • Contains YAML tool definitions that work across all patterns

Tool Naming Convention
Matimo supports both kebab-case and snake_case tool names for backward compatibility:

  • Legacy tools (GitHub, Gmail, older Slack): kebab-case (e.g., gmail-send-email, github-create-issue)
  • Modern tools (Bruno, meta-tools, newer providers): snake_case (e.g., bruno_run_request, matimo_create_tool)
  • Recommended for new tools: snake_case following the pattern {provider}_{action} (e.g., stripe_create_payment, notion_create_page)
  • Both naming styles work identically in all SDK patterns, filters need to check both: .startsWith('slack-') || .startsWith('slack_')

Step 1: Create a New Tool Provider Package

Section titled “Step 1: Create a New Tool Provider Package”
Terminal window
# Create provider directory
mkdir packages/github
cd packages/github
# Create package.json
cat > package.json << 'EOF'
{
"name": "@matimo/github",
"version": "0.1.0",
"description": "GitHub repository and issue management tools for Matimo",
"license": "MIT",
"type": "module",
"files": ["tools", "skills", "README.md", "definition.yaml"],
"peerDependencies": {
"matimo": "workspace:*"
},
"devDependencies": {
"@matimo/core": "workspace:*"
}
}
EOF
# Create tool directories
mkdir -p tools/create-issue
mkdir -p tools/list-repositories

Each tool gets its own directory with a definition.yaml file.

Create packages/github/tools/create-issue/definition.yaml:

name: github-create-issue
description: Create a new issue in a GitHub repository
version: '1.0.0'
parameters:
owner:
type: string
required: true
description: GitHub repository owner
repo:
type: string
required: true
description: GitHub repository name
title:
type: string
required: true
description: Issue title
body:
type: string
required: false
description: Issue description
execution:
type: http
method: POST
url: https://api.github.com/repos/{owner}/{repo}/issues
headers:
Authorization: 'Bearer {GITHUB_TOKEN}'
Accept: application/vnd.github.v3+json
body:
title: '{title}'
body: '{body}'
authentication:
type: api_key
location: header
name: Authorization
output_schema:
type: object
properties:
id:
type: number
description: Issue ID
number:
type: number
description: Issue number
title:
type: string
description: Issue title
url:
type: string
description: Issue URL
required:
- id
- number
- title

Required fields:

  • name - Unique tool identifier (lowercase; see naming)
  • description - What the tool does
  • version - Semantic version
  • parameters - Input schema with types, required flags
  • execution - How to execute (type, method, url/command, etc.)

Optional fields:

  • authentication - Auth method (api_key, bearer, oauth2, basic)
  • output_schema - Response validation schema
  • error_handling - Retry policy. Accepted and stored, but not applied by the executors yet
  • examples - Usage examples

See Tool Specification for complete schema.

Step 3: Add Provider Configuration (OAuth2 only)

Section titled “Step 3: Add Provider Configuration (OAuth2 only)”

A definition.yaml at the package root is a provider manifest. It is not a tool and it does not list your tools (Matimo finds them in tools/). Add one only if your tools use OAuth2 and you want OAuth2Handler to know the authorization and token URLs. This is the shape @matimo/github ships:

name: github-provider
type: provider
version: '1.0.0'
description: GitHub OAuth2 provider configuration
provider:
name: github
displayName: GitHub
endpoints:
authorizationUrl: https://github.com/login/oauth/authorize
tokenUrl: https://github.com/login/oauth/access_token

A tool refers to it with authentication: { type: oauth2, provider: github }. Tools that take an API key or token in a header (see Tool Specification) don’t need a manifest.

From typescript/ (the pnpm workspace; Python: make validate-tools from python/):

Terminal window
# Validate all tool definitions (YAML syntax + schema + governance rules)
pnpm validate-tools
# Run tests
pnpm test
# Build the package
pnpm build
  • YAML syntax is valid
  • All required fields present (name, description, version, execution)
  • Parameter types are valid (string, number, boolean, etc.)
  • Execution config is complete
  • HTTP DELETE tools declare requires_approval: true
  • Function tools declare risk: low | medium | high | critical

The validator does not call the API, so it cannot tell whether output_schema matches real responses, cover that in tests.

Terminal window
# Build everything
pnpm build
# Publish the provider package
npm publish packages/github --access public
# Or for pre-release versions
npm publish packages/github --access public --tag alpha

Publishing checklist:

  • Package name starts with @matimo/
  • Version number in package.json updated
  • All YAML files validate
  • Tests pass
  • Documentation in provider README
  • CHANGELOG updated
Terminal window
# Install via CLI
matimo install github
# Or directly with npm
npm install @matimo/github

Users can auto-discover all installed tool packages:

import { MatimoInstance } from 'matimo';
// Auto-discover finds all @matimo/* packages in node_modules
const matimo = await MatimoInstance.init({ autoDiscover: true });
// github-create-issue is now available!
const result = await matimo.execute('github-create-issue', {
owner: 'tallclub',
repo: 'matimo',
title: 'Amazing new feature',
body: 'This would be a great addition!',
});
const issue = (result as { data: { number: number; html_url: string } }).data;
console.log(`Issue #${issue.number} created: ${issue.html_url}`);

When autoDiscover: true:

  1. Scans node_modules/@matimo/ for installed packages
  2. Discovers tools/ directories in each package
  3. Loads all YAML definitions from those directories
  4. Validates each definition against the tool schema
  5. Registers tools in the tool registry
  6. Exposes them via matimo.listTools(), matimo.getTool(), matimo.execute()
const matimo = await MatimoInstance.init({ autoDiscover: true });
// Finds and registers:
// node_modules/@matimo/slack/tools/* → Slack tools
// node_modules/@matimo/github/tools/* → GitHub tools
// node_modules/@matimo/notion/tools/* → Notion tools
// ... any other @matimo/* packages
console.log(matimo.listTools()); // All tools from all packages
Terminal window
matimo list

Output:

Installed Matimo Packages:
@matimo/slack
Slack workspace tools
Tools: slack_send_channel_message, slack-list-channels, slack_get_channel_history, ...
@matimo/github
GitHub repository and issue management tools
Tools: github-create-issue, github-list-repositories, github-get-issue, ...
Total: 2 packages installed
Terminal window
matimo search email

Output:

Results:
@matimo/gmail
Gmail email tools
Tools: gmail-send-email, gmail-list-messages, gmail-create-draft, ...
Terminal window
matimo install twilio notion hubspot
packages/github/
├── tools/
│ ├── create-issue/
│ │ ├── definition.yaml # Tool definition
│ │ └── (index.ts - optional) # Custom logic if needed
│ ├── list-repositories/
│ │ └── definition.yaml
│ └── get-issue/
│ └── definition.yaml
├── definition.yaml # Provider manifest (OAuth2 endpoints), optional
├── package.json # npm package config
├── tsconfig.json # TypeScript config (if needed)
└── README.md # Provider documentation

For shell commands with parameter templating:

execution:
type: command
command: node
args:
- scripts/process.js
- '{param1}'
- '{param2}'
timeout: 30000

Only args are templated; command must be a fixed executable. Every call to a command tool asks for approval unless the YAML says requires_approval: false. The process gets the parent environment plus per-call credentials (an execution.env map works in Python only).

For REST APIs with authentication:

execution:
type: http
method: POST
url: https://api.example.com/items
headers:
Authorization: 'Bearer {API_TOKEN}'
Content-Type: application/json
body:
name: '{name}'
description: '{description}'
timeout: 15000

Matimo supports two patterns for function execution:

Section titled “1. Recommended: External TypeScript/JavaScript Files (Trusted)”
execution:
type: function
code: ./handler.ts # Path to external .ts or .js file
timeout: 10000

The referenced file should export a default async function. Its optional second argument holds the call’s credentials and the caller’s policy context ({ credentials?, policyContext? }), set by the host, never by the agent:

handler.ts
import type { FunctionToolContext } from '@matimo/core';
export default async function handler(params: Record<string, unknown>, context?: FunctionToolContext) {
const { query } = params;
// context?.policyContext?.agentId, roles, environment
return { result: 'success' };
}

Declare risk: on every function tool, the validator rejects one without it. The Python SDK runs handler.py next to the YAML: def run(params, context=None) (sync or async). See Tool Specification.

Security: Recommended. Code is version-controlled, reviewable, and cannot be injected via YAML.

2. Legacy: Embedded Code (Disabled by Default)

Section titled “2. Legacy: Embedded Code (Disabled by Default)”
execution:
type: function
code: |
async (params) => {
return { result: params.value };
}
timeout: 10000

SECURITY WARNING: Embedded code execution is disabled by default because it:

  • Runs arbitrary JavaScript with access to fs, path, axios modules
  • Creates an RCE (Remote Code Execution) vector if YAML comes from untrusted sources
  • Cannot be audited without parsing the YAML

How it works:

// Logic in function-executor.ts
const embeddedCodeDisabled = process.env.MATIMO_ALLOW_EMBEDDED_CODE !== 'true';
// If MATIMO_ALLOW_EMBEDDED_CODE is NOT set to 'true' → disabled (default)
// If MATIMO_ALLOW_EMBEDDED_CODE = 'true' → enabled (opt-in)
if (embeddedCodeDisabled) {
throw new MatimoError('Embedded code execution is disabled by default...');
}

To enable (NOT recommended):

Terminal window
export MATIMO_ALLOW_EMBEDDED_CODE=true

Only enable if you fully trust all tool YAML sources. Never enable in production without careful review.

Tools support multiple authentication methods:

authentication:
type: api_key
location: header
name: Authorization
# Loads from env: MATIMO_GITHUB_API_KEY or GITHUB_API_KEY
authentication:
type: bearer
location: header
# Loads from env: MATIMO_GITHUB_TOKEN or GITHUB_TOKEN
authentication:
type: oauth2
provider: github
scopes:
- repo
- issues
authentication:
type: basic
# Loads from env: MATIMO_GITHUB_USERNAME, MATIMO_GITHUB_PASSWORD
  • Package name: @matimo/{provider} (e.g., @matimo/slack)
  • Tool name: {provider}-{action} (e.g., slack-send-message)
  • Parameter names: lowercase, hyphens for multi-word
parameters:
channel_id:
type: string
required: true
description: Slack channel ID (e.g., C123456)
enum: [] # Optional: restrict to specific values

You can declare retry settings so the intent is recorded with the tool, but neither SDK applies them yet: a failed call is not retried. Handle RATE_LIMIT_EXCEEDED and retryable errors in the caller (see Errors).

error_handling:
retry: 2 # accepted, not yet applied
backoff_type: exponential # exponential, linear or fixed
initial_delay_ms: 1000

Validate responses to catch API changes:

output_schema:
type: object
properties:
id:
type: number
created_at:
type: string
format: date-time
required:
- id

See these working examples:

  • Slack tools: packages/slack/tools/
  • Gmail tools: packages/gmail/tools/
  • Calculator tool: packages/core/tools/
  1. Ensure tests pass: pnpm test
  2. Validate tools: pnpm validate-tools
  3. Update version in package.json
  4. Build: pnpm build
  5. Publish: npm publish packages/github --access public

Always use @matimo/{provider} scope for consistency and discoverability.

We’d love to include your tools in the ecosystem! Submit a PR with:

  1. Tool definitions in packages/{provider}/
  2. Tests for your tools
  3. Documentation in provider README
  4. Examples showing how to use them

See Contributing Guidelines for details.