Matimo OAuth2 Implementation Guide
Complete reference for OAuth2 in Matimo: architecture, token injection, provider configuration, troubleshooting, and best practices.
π Table of Contents
- Overview
- Architecture
- OAuth2 Flow
- Provider YAML Configuration
- Token Injection System
- Implementation Details
- Security Considerations
- Multi-Provider Support
- Examples
- Troubleshooting
Overview
Design Principles
Matimoβs OAuth2 implementation follows these core principles:
- Stateless Design: Matimo does NOT store tokens. Users provide tokens via environment variables.
- YAML-Driven Configuration: Provider OAuth endpoints and settings are defined in YAML, not hardcoded.
- Generic Parameter Injection: Works for ANY provider (Gmail, GitHub, Slack) without hardcoding provider-specific logic.
- Framework-Agnostic: OAuth flows happen outside Matimo; Matimo handles token injection and usage.
- Scalable: Supports unlimited providers by adding YAML files only.
Why This Approach?
Traditional OAuth implementations are monolithic and provider-specific. Matimo separates concerns:
- OAuth Flows (user authentication, token refresh) β Handled outside Matimo
- Token Storage β Userβs responsibility (environment variables, secure vaults)
- Token Usage β Matimoβs responsibility (auto-inject into tool execution)
This keeps Matimo lightweight and lets users manage OAuth however they prefer.
Architecture
System Layers
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β User Code / Framework (LangChain, CrewAI, custom) β
β ββ Sets: GMAIL_ACCESS_TOKEN=token123 β
β ββ Calls: matimo.execute('gmail-send-email', {to, subject}) β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MatimoInstance (src/matimo-instance.ts) β
β ββ execute(toolName, params) β
β ββ 1. Load tool from YAML β
β ββ 2. injectAuthParameters(tool, params) β KEY STEP β
β β ββ Pattern matching: looks for TOKEN/KEY/SECRET parameters β
β β ββ Environment lookup: PARAM_NAME β
β β ββ Merges found tokens into execution params β
β ββ 3. Call executor (HttpExecutor, CommandExecutor) β
β ββ 4. Return result β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Tool YAML Definition (tools/provider/tool-name.yaml) β
β ββ execution: β
β β ββ type: http β
β β ββ url: https://gmail.googleapis.com/gmail/v1/users/me/... β
β β ββ headers: β
β β β ββ Authorization: "Bearer {GMAIL_ACCESS_TOKEN}" β
β β ββ query_params: {...} β
β β ββ parameter_encoding: {...} β
β ββ authentication: β
β β ββ type: oauth2 β
β β ββ provider: gmail β
β β ββ required_scopes: [...] β
β ββ parameters: {...} β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Provider Config YAML (tools/provider/definition.yaml) β
β ββ Centralized OAuth2 endpoints β
β ββ Example for Gmail: β
β β ββ token_url: https://oauth2.googleapis.com/token β
β β ββ auth_url: https://accounts.google.com/o/oauth2/auth β
β β ββ scopes: {gmail: [...], drive: [...]} β
β β ββ redirect_uri: https://localhost:3000/callback β
β ββ Used by OAuth2ProviderLoader β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Core Components
| Component | Purpose | Location |
|---|---|---|
| MatimoInstance | Main SDK class; orchestrates execution | src/matimo-instance.ts |
| injectAuthParameters() | Pattern-based token injection | src/matimo-instance.ts |
| OAuth2ProviderLoader | Loads provider config from YAML | src/auth/oauth2-provider-loader.ts |
| OAuth2ProviderConfig | In-memory provider configuration | src/auth/oauth2-provider-config.ts |
| ToolDefinition | Type for tool schemas | src/core/types.ts |
| HttpExecutor | Executes HTTP requests | src/executors/http-executor.ts |
OAuth2 Flow
Standard OAuth2 Authorization Flow
Matimo supports the Authorization Code Flow (most common for web apps and CLI tools):
User Application Google OAuth Server
β β
1. Redirect to Google β
GET https://accounts.google.com/o/oauth2/auth
?client_id=YOUR_CLIENT_ID
&redirect_uri=http://localhost:3000/callback
&scope=https://www.googleapis.com/auth/gmail.send
&response_type=code
βββββββββββββββββββββββββββββββββββββββββββββ
β
2. User clicks "Allow"
β
3. Browser redirected to callback ββββββββ
with authorization code (auth_code)
β
4. Exchange code for token (backend)
POST https://oauth2.googleapis.com/token
{
code: auth_code,
client_id: YOUR_CLIENT_ID,
client_secret: YOUR_CLIENT_SECRET,
grant_type: "authorization_code"
}
βββββββββββββββββββββββββββββββββββββββββββββ
β
5. Receive access_token + refresh_token βββββ
β
6. Store access_token (env var, secure vault)
β
7. Use with Matimo:
process.env.GMAIL_ACCESS_TOKEN = access_token
await matimo.execute('gmail-send-email', {...})
Matimoβs Role in the Flow
What Matimo DOES:
- β Inject tokens into tool execution (step 7)
- β Validate token is present before execution
- β Handle token in HTTP headers correctly
- β Support token override at runtime
What Matimo DOES NOT Do:
- β Perform OAuth authorization (steps 1-2)
- β Exchange code for token (step 4)
- β Store tokens (step 6)
- β Refresh expired tokens
- β Handle user login/logout
Why? Matimo is framework-agnostic and stateless. OAuth flows vary by framework and security requirements.
Token Lifecycle in Matimo
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. User obtains token (outside Matimo) β
β - Via OAuth playground β
β - Via OAuth library (google-auth-library, etc.) β
β - Via custom OAuth flow β
ββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Store token in environment β
β export GMAIL_ACCESS_TOKEN=ya29.a0AXooCg... β
ββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Matimo injectAuthParameters() β
β - Scans tool's execution config for {PARAM} placeholders β
β - Finds {GMAIL_ACCESS_TOKEN} β
β - Looks up env: GMAIL_ACCESS_TOKEN β
β - Injects into request headers/body β
ββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. HttpExecutor sends request with token β
β Authorization: Bearer ya29.a0AXooCg... β
ββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. API validates token, executes request β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Provider YAML Configuration
Provider Definition File Structure
Each provider has a central configuration file: tools/{provider}/definition.yaml
Example: Gmail Provider (tools/gmail/definition.yaml)
provider:
name: gmail
description: Google Gmail API
documentation_url: https://developers.google.com/gmail/api
oauth2:
# OAuth2 endpoints
auth_url: https://accounts.google.com/o/oauth2/auth
token_url: https://oauth2.googleapis.com/token
revoke_url: https://oauth2.googleapis.com/revoke
# Scopes for different permission levels
scopes:
send:
- https://www.googleapis.com/auth/gmail.send
- https://www.googleapis.com/auth/gmail.readonly
draft:
- https://www.googleapis.com/auth/gmail.modify
full_access:
- https://www.googleapis.com/auth/gmail
# Default redirect URI for local dev
redirect_uri: http://localhost:3000/callback
# If your app needs specific settings
approval_prompt: force # Force consent screen
access_type: offline # Get refresh token
# Override mechanism (defined in tool YAML)
parameter_mapping:
# Maps parameter names to environment variable patterns
- parameter: GMAIL_ACCESS_TOKEN
env_pattern: 'GMAIL_ACCESS_TOKEN'
required: true
description: 'Gmail OAuth access token'
Supported Provider Fields
| Field | Type | Required | Description |
|---|---|---|---|
provider.name |
string | Yes | Provider identifier (gmail, github, slack) |
provider.description |
string | No | Human-readable description |
oauth2.auth_url |
string | Yes | Authorization endpoint |
oauth2.token_url |
string | Yes | Token exchange endpoint |
oauth2.revoke_url |
string | No | Token revocation endpoint |
oauth2.scopes |
object | Yes | Available OAuth scopes (grouped by feature) |
oauth2.redirect_uri |
string | Yes | Redirect URI after user authorizes |
oauth2.approval_prompt |
string | No | βforceβ to always show consent screen |
oauth2.access_type |
string | No | βofflineβ to get refresh token |
Token Injection System
How Matimo Auto-Injects Tokens
The injectAuthParameters() method in MatimoInstance performs three steps:
Step 1: Extract Parameter Placeholders
Scan the toolβs execution config for {PARAM_NAME} patterns:
execution:
type: http
url: 'https://gmail.googleapis.com/...'
headers:
Authorization: 'Bearer {GMAIL_ACCESS_TOKEN}' # β Found!
body:
message: '{COMPOSED_MESSAGE}' # β Found!
Result: Array of found parameters: ["GMAIL_ACCESS_TOKEN", "COMPOSED_MESSAGE"]
Step 2: Identify Auth Parameters
Filter by pattern matching (parameters are auth-related if they contain):
TOKENKEYSECRETPASSWORDCREDENTIALAPIKEY
Example:
- β
GMAIL_ACCESS_TOKENβ Contains βTOKENβ β Auth parameter - β
API_KEYβ Contains βKEYβ β Auth parameter - β
COMPOSED_MESSAGEβ No match β User parameter (not injected)
Step 3: Environment Lookup & Injection
For each auth parameter, look up environment variables:
// Parameter: GMAIL_ACCESS_TOKEN
// Try 1: prefix (recommended)
process.env.GMAIL_ACCESS_TOKEN;
// Falls back to:
// Try 2: Direct name
process.env.GMAIL_ACCESS_TOKEN;
If found, inject into execution parameters.
Code Implementation
// In src/matimo-instance.ts
private injectAuthParameters(
tool: ToolDefinition,
params: Record<string, any>
): Record<string, any> {
// Step 1: Extract parameter placeholders from execution config
const placeholders = this.extractParameterPlaceholders(
tool.execution
);
// Step 2 & 3: For each placeholder, attempt to inject from environment
const injected = { ...params };
for (const placeholder of placeholders) {
// Check if this looks like an auth parameter
if (this.isAuthParameter(placeholder)) {
// Try prefix first, then fallback to direct name
const envKey = `${placeholder}`;
let value = process.env[envKey] || process.env[placeholder];
if (value) {
injected[placeholder] = value;
}
}
}
return injected;
}
private extractParameterPlaceholders(
obj: any
): string[] {
const placeholders: string[] = [];
const regex = /\{(\w+)\}/g;
const scan = (value: any) => {
if (typeof value === 'string') {
let match;
while ((match = regex.exec(value)) !== null) {
placeholders.push(match[1]);
}
} else if (typeof value === 'object' && value !== null) {
Object.values(value).forEach(scan);
}
};
scan(obj);
return [...new Set(placeholders)]; // Deduplicate
}
private isAuthParameter(name: string): boolean {
const authPatterns = [
'TOKEN', 'KEY', 'SECRET', 'PASSWORD',
'CREDENTIAL', 'APIKEY', 'AUTH'
];
return authPatterns.some(pattern =>
name.toUpperCase().includes(pattern)
);
}
Token Injection Examples
Example 1: Gmail Send Email
Tool YAML:
name: send-email
execution:
type: http
method: post
url: https://gmail.googleapis.com/gmail/v1/users/me/messages/send
headers:
Authorization: 'Bearer {GMAIL_ACCESS_TOKEN}'
body:
raw: '{ENCODED_MESSAGE}'
User Code:
process.env.GMAIL_ACCESS_TOKEN = 'ya29.a0AXooCg...';
await matimo.execute('gmail-send-email', {
to: 'user@example.com',
subject: 'Hello',
body: 'Test message',
// GMAIL_ACCESS_TOKEN NOT needed - auto-injected!
});
What Happens:
- User provides:
to,subject,body - Matimo extracts placeholders:
GMAIL_ACCESS_TOKEN,ENCODED_MESSAGE - Matimo identifies
GMAIL_ACCESS_TOKENas auth parameter (contains βTOKENβ) - Matimo looks up
GMAIL_ACCESS_TOKENβ found - Matimo injects:
GMAIL_ACCESS_TOKEN: "ya29.a0AXooCg..." - Request sent with
Authorization: Bearer ya29.a0AXooCg...
Example 2: GitHub Repository API
Tool YAML:
name: get-repo-info
execution:
type: http
url: https://api.github.com/repos/{owner}/{repo}
headers:
Authorization: 'Bearer {GITHUB_API_KEY}'
Accept: 'application/vnd.github.v3+json'
User Code:
process.env.GITHUB_API_KEY = 'ghp_abc123...';
await matimo.execute('get-repo-info', {
owner: 'torvalds',
repo: 'linux',
// GITHUB_API_KEY auto-injected
});
Implementation Details
Files & Locations
| File | Purpose |
|---|---|
src/matimo-instance.ts |
Main SDK class, contains injectAuthParameters() |
src/auth/oauth2-provider-config.ts |
OAuth2 provider configuration loader |
src/auth/oauth2-provider-loader.ts |
YAML provider file parser |
src/core/types.ts |
ToolDefinition interface |
src/executors/http-executor.ts |
HTTP request execution |
tools/{provider}/definition.yaml |
Provider OAuth configuration |
tools/{provider}/{tool}.yaml |
Individual tool definitions |
OAuth2ProviderLoader
// Load provider config from YAML
const loader = new OAuth2ProviderLoader('./tools');
const gmailConfig = await loader.loadProvider('gmail');
// Result:
{
name: 'gmail',
oauth2: {
auth_url: '...',
token_url: '...',
scopes: { ... }
}
}
OAuth2ProviderConfig
// In-memory provider config storage
const config = new OAuth2ProviderConfig();
// Register provider
await config.addProvider('gmail', gmailConfig);
// Retrieve provider
const provider = config.getProvider('gmail');
// List all providers
const all = config.getAllProviders();
Security Considerations
β Security Best Practices
1. Token Storage
DONβT:
# β Bad: Token in code
const token = "ya29.a0AXooCg...";
DO:
# β
Good: Token in environment variable
export GMAIL_ACCESS_TOKEN="ya29.a0AXooCg..."
# β
Better: Use .env file with .gitignore
# .env (never commit)
GMAIL_ACCESS_TOKEN=ya29.a0AXooCg...
# .gitignore
.env
.env.local
β BEST: Secure Vault
# Use 1Password, LastPass, AWS Secrets Manager, etc.
# Retrieve at runtime:
const token = await vault.getSecret('matimo-gmail-token');
process.env.GMAIL_ACCESS_TOKEN = token;
2. Token Permissions (Scopes)
Always request minimum required scopes:
# β Bad: Full access
scopes:
- https://www.googleapis.com/auth/gmail
# β
Good: Specific scopes
scopes:
send:
- https://www.googleapis.com/auth/gmail.send # Send only
read:
- https://www.googleapis.com/auth/gmail.readonly # Read only
3. Token Expiration
Monitor token expiration and refresh:
// Token expiration typically: 1 hour for access tokens
// What to do when expired:
// Option 1: Refresh token (Phase 2 feature)
const newToken = await oauth2.refreshToken(refreshToken);
process.env.GMAIL_ACCESS_TOKEN = newToken;
// Option 2: Re-authorize
console.error('Token expired. Please re-authenticate:');
console.log('Visit: https://oauth2.example.com/authorize');
4. Token Revocation
Always revoke tokens when done:
// After tool execution completes
await oauth2.revokeToken(accessToken, {
provider: 'gmail',
revoke_url: 'https://oauth2.googleapis.com/revoke',
});
5. Log Sanitization
Never log tokens:
// β Bad: Token in logs
logger.info('Sending email', {
token: params.GMAIL_ACCESS_TOKEN, // DANGER!
to: 'user@example.com',
});
// β
Good: Sanitize sensitive data
const sanitized = {
to: params.to,
subject: params.subject,
// token: never logged
};
logger.info('Sending email', sanitized);
π‘οΈ Attack Mitigation
Cross-Site Request Forgery (CSRF) Protection
When implementing OAuth flow (Phase 2):
// Generate random state parameter
const state = crypto.randomBytes(32).toString('hex');
session.setState(state);
// Verify state in callback
if (request.query.state !== session.getState()) {
throw new Error('State mismatch - CSRF attack?');
}
Token Leakage Prevention
// β Never expose tokens in URLs
GET /api/tool?token=ya29.a0AXooCg...
// β
Always use headers or POST body
headers: {
Authorization: 'Bearer ya29.a0AXooCg...'
}
Secret Rotation
# tools/gmail/definition.yaml
oauth2:
client_id: ${GMAIL_CLIENT_ID} # From env
client_secret: ${GMAIL_CLIENT_SECRET} # From env (rotate regularly)
# Schedule periodic secret rotation in your OAuth app settings
Multi-Provider Support
Adding a New Provider
Matimo supports unlimited providers without code changes.
Step 1: Create Provider Config
Create tools/{provider}/definition.yaml:
# tools/github/definition.yaml
provider:
name: github
description: GitHub API
documentation_url: https://docs.github.com/en/rest
oauth2:
auth_url: https://github.com/login/oauth/authorize
token_url: https://github.com/login/oauth/access_token
revoke_url: https://api.github.com/applications/{client_id}/token
scopes:
read:
- repo:status
- public_repo
write:
- repo
admin:
- admin:repo_hook
- admin:user
redirect_uri: http://localhost:3000/callback
access_type: offline # Get refresh token
Step 2: Create Tool Definitions
Create tools/github/{tool}.yaml files:
# tools/github/get-repo.yaml
name: get-repo
description: Get GitHub repository information
version: '1.0.0'
parameters:
owner:
type: string
required: true
description: Repository owner
repo:
type: string
required: true
description: Repository name
execution:
type: http
method: get
url: 'https://api.github.com/repos/{owner}/{repo}'
headers:
Authorization: 'Bearer {GITHUB_API_KEY}'
Accept: 'application/vnd.github.v3+json'
authentication:
type: oauth2
provider: github
required_scopes:
- public_repo
output_schema:
type: object
properties:
name:
type: string
full_name:
type: string
stars:
type: integer
description:
type: string
Step 3: Use in Code
No code changes needed!
// Matimo automatically discovers and loads tools/github/*.yaml
const tools = await loader.loadToolsFromDirectory('./tools');
// GitHub tools are now available
await matimo.execute('get-repo', {
owner: 'torvalds',
repo: 'linux',
// GITHUB_API_KEY auto-injected from GITHUB_API_KEY
});
Runtime Override Example
// Override provider endpoint at runtime
const tool = tools.find((t) => t.name === 'get-repo');
// Option 1: Env variable
process.env.GITHUB_API_KEY = 'custom-token';
// Option 2: Runtime argument
const result = await matimo.execute('get-repo', {
owner: 'torvalds',
repo: 'linux',
GITHUB_API_KEY: 'custom-token', // Overrides env
});
Token Passing Methods
How Users Can Pass Tokens
Users have multiple flexible options to pass tokens to Matimo - not just environment variables:
Option 1: Explicit Parameter (Highest Priority)
Pass the token directly in the execute call. This is the most flexible and secure approach:
const token = await getTokenFromVault('gmail'); // Get from vault, database, etc.
await matimo.execute('send-email', {
to: 'user@example.com',
subject: 'Hello',
body: 'Test',
GMAIL_ACCESS_TOKEN: token, // β
Explicit - takes precedence!
});
Advantages:
- β Most secure (tokens not stored in env)
- β Per-request flexibility
- β Multi-tenant support
- β Works with secure vaults
Use cases: Production, microservices, multi-tenant apps
Option 2: Runtime Environment Variable
Set the environment variable at runtime from a vault or database:
// Fetch token at startup
const token = await secretsManager.getSecret('matimo-gmail-token');
// Set in environment
process.env.GMAIL_ACCESS_TOKEN = token;
// Token auto-injected in all execute calls
await matimo.execute('send-email', {
to: 'user@example.com',
subject: 'Hello',
body: 'Test',
// Token auto-injected from process.env
});
Advantages:
- β Clean code (auto-injection)
- β Simple to implement
- β Works across app
Use cases: Development, single-tenant apps, startups
Option 3: Combination (Best Practice)
Use both approaches together:
// At app startup - set env var
process.env.GMAIL_ACCESS_TOKEN = initialToken;
// Later - override for specific request if needed
await matimo.execute('send-email', {
to: 'user@example.com',
subject: 'Hello',
body: 'Test',
GMAIL_ACCESS_TOKEN: differentToken, // Override for this request
});
// Other requests - use env var (auto-injected)
await matimo.execute('send-email', {
to: 'other@example.com',
subject: 'Hello',
body: 'Test',
// Uses GMAIL_ACCESS_TOKEN from env
});
Token Resolution Priority
When Matimo needs a token, it checks in this order:
1. Explicit parameter (highest priority)
ββ await matimo.execute('tool', { GMAIL_ACCESS_TOKEN: 'explicit' })
2. Environment variable (fallback)
ββ process.env.GMAIL_ACCESS_TOKEN
ββ process.env.GMAIL_ACCESS_TOKEN (if not found)
3. Not found (lowest priority)
ββ Error: "Missing required parameter: GMAIL_ACCESS_TOKEN"
The first match wins - others are ignored.
Real-World Examples
Example: Secure Vault (AWS Secrets Manager)
import AWS from 'aws-sdk';
const secretsManager = new AWS.SecretsManager();
async function executeWithVaultToken(toolName, params) {
// Fetch token from AWS Secrets Manager at runtime
const secret = await secretsManager.getSecretValue({
SecretId: 'matimo-gmail-token',
});
const token = JSON.parse(secret.SecretString).token;
// Pass explicitly - never stored in code or env
return await matimo.execute(toolName, {
...params,
GMAIL_ACCESS_TOKEN: token,
});
}
// Usage
await executeWithVaultToken('send-email', {
to: 'user@example.com',
subject: 'Hello',
body: 'Test',
});
Example: Multi-Tenant Application
// Different token per user/tenant
async function sendEmailForUser(userId, mailParams) {
const user = await db.users.findById(userId);
const userToken = user.gmail_access_token; // From database
return await matimo.execute('send-email', {
...mailParams,
GMAIL_ACCESS_TOKEN: userToken, // User-specific token
});
}
// Each user has their own token
await sendEmailForUser('user-1', { to, subject, body });
await sendEmailForUser('user-2', { to, subject, body });
Example: Token Refresh Handler
class TokenManager {
private currentToken: string;
async ensureValidToken() {
// Check if token expired
if (this.isTokenExpired(this.currentToken)) {
// Refresh and update
this.currentToken = await this.refreshAccessToken();
process.env.GMAIL_ACCESS_TOKEN = this.currentToken;
}
return this.currentToken;
}
async execute(toolName, params) {
const token = await this.ensureValidToken();
return await matimo.execute(toolName, {
...params,
GMAIL_ACCESS_TOKEN: token, // Always fresh
});
}
}
// Usage
const tokenManager = new TokenManager();
await tokenManager.execute('send-email', { to, subject, body });
Example: OAuth Callback Handler (Phase 2 Preparation)
async function handleOAuthCallback(code, userId) {
// Exchange code for token
const tokens = await oauth2.exchangeCode(code);
// Store tokens (choose one approach)
// Option A: Database
await db.users.update(userId, {
gmail_access_token: tokens.access_token,
gmail_refresh_token: tokens.refresh_token,
token_expires_at: Date.now() + tokens.expires_in * 1000,
});
// Option B: Secure Vault (AWS Secrets Manager, 1Password, etc.)
await vault.setSecret(`user-${userId}-gmail-token`, tokens.access_token);
// Later, when executing tools:
const token = await db.users.findById(userId).gmail_access_token;
// OR
const token = await vault.getSecret(`user-${userId}-gmail-token`);
// Pass to Matimo
await matimo.execute('send-email', {
to: 'user@example.com',
subject: 'Hello',
body: 'Test',
GMAIL_ACCESS_TOKEN: token, // Explicit token
});
}
Comparison: Which Approach to Use?
| Approach | Pros | Cons | Best For |
|---|---|---|---|
| Explicit Parameter | π Secure, flexible, per-request, no env vars | More verbose | Production, multi-tenant, microservices |
| Runtime Environment | β¨ Clean code, auto-injection, simple | Token in memory, app-wide | Development, single-tenant, startups |
| Vault/Secrets Manager | π‘οΈ Enterprise-secure, auditable, centralized | Extra infrastructure, complexity | Enterprise apps, compliance-required |
| OAuth Flow | π User-authenticated, standards-based | Phase 2 (not yet) | Future production apps |
Examples
Example 1: Gmail with Factory Pattern
// examples/tools/gmail/gmail-factory.ts
import { MatimoInstance } from matimo;
// Initialize Matimo with tools
const matimo = await MatimoInstance.init('./tools');
// Gmail access token from environment
process.env.MATIMO_GMAIL_ACCESS_TOKEN = 'ya29.a0AXooCg...';
// Execute without passing token explicitly
const result = await matimo.execute('send-email', {
to: 'recipient@example.com',
subject: 'Hello from Matimo',
body: 'This is a test message'
// Token auto-injected!
});
console.log('Email sent:', result.id);
Example 2: GitHub with Decorator Pattern
// Example: Decorator auto-injects GITHUB_API_KEY
import { tool } from '../../../src';
@tool('get-repo')
async getGithubRepo(owner: string, repo: string) {
return undefined; // Matimo intercepts
}
// Set token
process.env.GITHUB_API_KEY = 'ghp_abc123...';
// Call - no token needed
const repo = await getGithubRepo('torvalds', 'linux');
console.log(repo.name); // 'linux'
Example 3: AI Agent Deciding Which Tool to Use
// examples/tools/gmail/gmail-langchain.ts
import { MatimoInstance, convertToolsToLangChain } from 'matimo';
import { ChatOpenAI } from '@langchain/openai';
import { createAgent } from 'langchain/agents';
const matimo = await MatimoInstance.init('./tools');
// Get Gmail-specific tools
const gmailTools = matimo.listTools().filter((t) => t.name.startsWith('gmail-'));
// Convert to LangChain format with OAuth token
const langchainTools = await convertToolsToLangChain(gmailTools, matimo, {
GMAIL_ACCESS_TOKEN: process.env.GMAIL_ACCESS_TOKEN!,
});
// Agent decides which tool to use based on user request
const agent = await createAgent({
model: new ChatOpenAI({ modelName: 'gpt-4' }),
tools: langchainTools,
});
// User says what they want in natural language
const result = await agent.invoke({
messages: [
{
role: 'user',
content: 'Send an email to alice@example.com saying hello',
},
],
});
// Agent chooses 'send-email' tool, passes parameters,
// Matimo injects GMAIL_ACCESS_TOKEN, email is sent!
Architecture Diagrams
Token Injection System Flow
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β User Code β
β process.env.GMAIL_ACCESS_TOKEN = "ya29.a0AXooCg..." β
β await matimo.execute('send-email', {to, subject, body}) β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MatimoInstance.execute() β
β 1. Load tool from registry β
β name: 'send-email' β
β execution: β
β headers: β
β Authorization: "Bearer {GMAIL_ACCESS_TOKEN}" β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β injectAuthParameters(tool, params) β
β Step 1: extractParameterPlaceholders(execution) β
β Result: ["GMAIL_ACCESS_TOKEN", ...] β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β isAuthParameter(placeholder) β
β Check if contains: TOKEN, KEY, SECRET, PASSWORD, etc. β
β "GMAIL_ACCESS_TOKEN" contains "TOKEN" β β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Environment Variable Lookup β
β Try: GMAIL_ACCESS_TOKEN β Found! β
β Value: "ya29.a0AXooCg..." β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Merge into Execution Parameters β
β { β
β GMAIL_ACCESS_TOKEN: "ya29.a0AXooCg...", β
β to: "user@example.com", β
β subject: "Hello", β
β body: "Test" β
β } β
βββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HttpExecutor.execute() β
β Replace {GMAIL_ACCESS_TOKEN} with actual token value β
β Headers: { β
β Authorization: "Bearer ya29.a0AXooCg..." β
β } β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Send HTTP Request to Gmail API β
β POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send β
β Headers: {Authorization: "Bearer ya29.a0AXooCg..."} β
βββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Return Result to User β
β { β
β id: "thread123", β
β threadId: "abc456", β
β labelIds: ["SENT"] β
β } β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
OAuth2 Standard Authorization Code Flow
ββββββββββββββββββββββ ββββββββββββββββββββββββ
β User's Browser β β Google Auth Server β
ββββββββββ¬ββββββββββββ ββββββββββββ¬ββββββββββββ
β β
β 1. User clicks "Sign in with Google" β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ>
β GET /oauth2/auth?client_id=...&scope=... β
β β
β 2. Google shows consent β
β screen β
β <βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β (User sees: "Allow app to send emails?") β
β β
β 3. User clicks "Allow" β
β β
β Redirect to callback with code β
β <βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Location: http://localhost:3000/callback?code=abc123
β
ββββββββββ΄ββββββββββββ
β Your Backend β 4. Exchange code for token
β /callback handler β POST https://oauth2.googleapis.com/token
ββββββββββ¬ββββββββββββ body: {code, client_id, client_secret}
β β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ>
β Google
β exchanges
β code
β β
β 5. Receive tokens β
β { β
β access_token: "ya29.a0AXooCg...", β
β refresh_token: "1//0gXKJV...", β
β expires_in: 3600 β
β } β
β <βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββ 6. Store tokens securely
(database, secure vault, env var)
β
ββ 7. Use with Matimo
process.env.GMAIL_ACCESS_TOKEN = token
matimo.execute('send-email', {...})
Environment Variable Resolution
Parameter Name: GMAIL_ACCESS_TOKEN
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Check 1: Prefixed (Recommended) β
β β
β process.env.GMAIL_ACCESS_TOKEN β
β β
β β If found β Use it β
β β β
β ββ> GMAIL_ACCESS_TOKEN = "ya29.a0AXooCg..." β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β NOT FOUND
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Check 2: Direct Name (Fallback) β
β β
β process.env.GMAIL_ACCESS_TOKEN β
β β
β β If found β Use it β
β β β
β ββ> GMAIL_ACCESS_TOKEN = "ya29.a0AXooCg..." β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β NOT FOUND
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β No Token Found β
β β
β Parameter not injected β
β Tool execution may fail if token was required β
β β
β Error: "Missing required parameter: GMAIL_ACCESS_TOKEN" β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Quick Reference
Getting Started (5 Steps)
- Obtain Token - Via OAuth Playground or your appβs auth flow
- Set Environment -
export GMAIL_ACCESS_TOKEN=token - Load Tools -
await loader.loadToolsFromDirectory() - Execute -
await matimo.execute('tool-name', params) - Handle Errors - Check error code and token validity
Common Patterns
| Pattern | Use Case | Token Passing |
|---|---|---|
| Factory | Scripts, direct SDK | Auto-inject from env |
| Decorator | Framework integration | Auto-inject from env |
| AI Agent | LLM-driven tools | Auto-inject from env |
| Environment File | Local development | Load from .env |
| Secure Vault | Production | Retrieve at runtime |
Environment Variable Names
Format: {PROVIDER}_{TYPE}
Examples:
GMAIL_ACCESS_TOKENGITHUB_API_KEYSLACK_BOT_TOKEN
Fallback: Direct name without `` prefix
Security Checklist
- Storage: Never hardcode tokens in source code
- Files: Add
.envto.gitignore - Logging: Never log tokens or include in error messages
- Scope: Request minimum required permissions only
- Expiration: Monitor token expiry and refresh periodically
- Rotation: Rotate tokens regularly (recommend: monthly)
- Revocation: Revoke tokens when no longer needed
- Transmission: Always use HTTPS/TLS for requests
Troubleshooting
Common Issues
| Symptom | Cause | Solution |
|---|---|---|
| βMissing required parameter: GMAIL_ACCESS_TOKENβ | Token env var not set | export GMAIL_ACCESS_TOKEN=token |
| β401 Unauthorizedβ | Invalid or expired token | Refresh token or re-authenticate |
| β403 Forbiddenβ | Insufficient scopes | Check token has required permissions |
| β400 Bad Requestβ | Malformed request | Verify parameter encoding and URL |
| βInvalid grantβ | Token not recognized | Token revoked or never issued |
| βToken expiredβ | Access token lifetime exceeded | Refresh token (Phase 2 feature) |
| Tool not found | Tool YAML doesnβt exist | Verify tools/{provider}/{tool}.yaml |
| Parameter mismatch | User param doesnβt match schema | Check toolβs parameter definitions |
Debug Steps
// 1. Check env var is set
console.log(process.env.GMAIL_ACCESS_TOKEN);
// 2. Check parameter name contains TOKEN/KEY/SECRET
const tool = tools.find((t) => t.name === 'send-email');
console.log(tool.execution);
// 3. Verify token format and expiration
// Access tokens typically valid for 1 hour
// 4. Test with explicit token override
await matimo.execute('send-email', {
to: 'user@example.com',
subject: 'Test',
body: 'Test',
GMAIL_ACCESS_TOKEN: 'explicit-token', // Bypass env lookup
});
// 5. Check token has required scopes
// Different tools need different scopes
Token Expiration & Refresh
// Token expires after ~1 hour
// Options until Phase 2 auto-refresh:
// Option 1: Refresh using OAuth library
const newToken = await oauth2.refreshToken(refreshToken);
process.env.GMAIL_ACCESS_TOKEN = newToken;
// Option 2: Re-authenticate at OAuth Playground
// Visit: https://developers.google.com/oauthplayground
// Copy fresh token to env var
// Option 3: Implement token refresh logic in your code
async function ensureValidToken() {
const token = process.env.GMAIL_ACCESS_TOKEN;
if (isExpired(token)) {
const newToken = await refreshToken();
process.env.GMAIL_ACCESS_TOKEN = newToken;
}
return token;
}
Adding New Providers
To add support for a new provider (GitHub, Slack, etc.):
-
Create Provider Config -
tools/{provider}/definition.yamlprovider: name: github oauth2: auth_url: https://github.com/login/oauth/authorize token_url: https://github.com/login/oauth/access_token scopes: - repo - admin:user -
Create Tool Definitions -
tools/{provider}/{tool}.yamlauthentication: type: oauth2 provider: github required_scopes: [repo] execution: headers: Authorization: 'Bearer {GITHUB_API_KEY}' -
Set Environment Variable -
export GITHUB_API_KEY=token -
Load & Execute - No code changes needed!
const result = await matimo.execute('get-repo', {...});
Last Updated: February 2, 2026
Matimo Phase: 1 (OAuth2 provider config and token injection)
Next Phase: Phase 2 - Automatic token refresh, secure storage, built-in OAuth flows