Initial commit: OpenClaw Mattermost Extension

This commit is contained in:
2026-04-10 21:57:29 -07:00
commit 2072259fb6
126 changed files with 26403 additions and 0 deletions
+864
View File
@@ -0,0 +1,864 @@
# API Reference
Complete reference for the Enhanced Mattermost Extension API.
## Table of Contents
- [Actions](#actions)
- [Interactive Directives](#interactive-directives)
- [Configuration Types](#configuration-types)
- [Error Handling](#error-handling)
- [Security Audit](#security-audit)
- [Client API](#client-api)
## Actions
### editMessage
Edit an existing message posted by the bot.
```typescript
import { editMessage } from '@lumbridgecorp/openclaw-mattermost';
const result = await editMessage({
cfg: OpenClawConfig,
postId: string,
channelId: string,
message?: string,
props?: Record<string, unknown>,
accountId?: string | null,
fetchImpl?: MattermostFetch
});
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `cfg` | `OpenClawConfig` | Yes | OpenClaw configuration object |
| `postId` | `string` | Yes | ID of the post to edit |
| `channelId` | `string` | Yes | Channel ID containing the post |
| `message` | `string` | No | New message content |
| `props` | `Record<string, unknown>` | No | Additional post properties |
| `accountId` | `string \| null` | No | Specific account to use |
| `fetchImpl` | `MattermostFetch` | No | Custom fetch implementation |
**Returns:**
```typescript
type EditMessageResult =
| { ok: true; postId: string; channelId: string }
| { ok: false; error: string; errorCode?: string };
```
**Error Codes:**
- `PERMISSION_DENIED` - Cannot edit other users' messages or system messages
- `POST_NOT_FOUND` - Post was deleted or doesn't exist
- `RATE_LIMITED` - Too many edit requests
- `AUTH_FAILED` - Invalid or expired token
**Example:**
```typescript
const result = await editMessage({
cfg: openclawConfig,
postId: 'abc123def456ghi789jkl012',
channelId: 'channel123',
message: 'Updated message content'
});
if (result.ok) {
console.log(`Edited post ${result.postId}`);
} else {
console.error(`Edit failed: ${result.error} (${result.errorCode})`);
}
```
---
### deleteMessage
Delete a message posted by the bot.
```typescript
import { deleteMessage } from '@lumbridgecorp/openclaw-mattermost';
const result = await deleteMessage({
cfg: OpenClawConfig,
postId: string,
channelId?: string,
accountId?: string | null,
fetchImpl?: MattermostFetch
});
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `cfg` | `OpenClawConfig` | Yes | OpenClaw configuration object |
| `postId` | `string` | Yes | ID of the post to delete |
| `channelId` | `string` | No | Channel ID (for validation) |
| `accountId` | `string \| null` | No | Specific account to use |
| `fetchImpl` | `MattermostFetch` | No | Custom fetch implementation |
**Returns:**
```typescript
type DeleteMessageResult = { ok: true } | { ok: false; error: string };
```
**Notes:**
- Returns `ok: true` if post is already deleted (idempotent)
- Cannot delete system messages
- Can only delete bot's own messages
**Example:**
```typescript
const result = await deleteMessage({
cfg: openclawConfig,
postId: 'abc123def456ghi789jkl012'
});
if (result.ok) {
console.log('Message deleted successfully');
} else {
console.error(`Delete failed: ${result.error}`);
}
```
---
### downloadFile
Download a file from Mattermost with security validations.
```typescript
import { downloadFile } from '@lumbridgecorp/openclaw-mattermost';
const result = await downloadFile({
cfg: OpenClawConfig,
fileId: string,
destinationPath: string,
maxSize?: number,
accountId?: string | null,
fetchImpl?: MattermostFetch,
allowedMimePrefixes?: string[]
});
```
**Parameters:**
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `cfg` | `OpenClawConfig` | Yes | - | OpenClaw configuration |
| `fileId` | `string` | Yes | - | Mattermost file ID |
| `destinationPath` | `string` | Yes | - | Local path to save file |
| `maxSize` | `number` | No | 100MB | Maximum file size in bytes |
| `accountId` | `string \| null` | No | - | Specific account to use |
| `fetchImpl` | `MattermostFetch` | No | - | Custom fetch implementation |
| `allowedMimePrefixes` | `string[]` | No | Built-in list | Allowed MIME types |
**Returns:**
```typescript
type DownloadFileResult =
| { ok: true; filePath: string; fileId: string; metadata: FileMetadata }
| { ok: false; error: string; errorCode?: string };
type FileMetadata = {
id: string;
name: string;
mimeType: string;
size: number;
extension: string;
};
```
**Error Codes:**
- `CONFIG_ERROR` - Missing botToken or baseUrl
- `INVALID_PARAMS` - Missing fileId or destinationPath
- `FILE_TOO_LARGE` - File exceeds maxSize
- `EMPTY_FILE` - File has no content
- `BLOCKED_FILE_TYPE` - Executable file extension
- `BLOCKED_MIME_TYPE` - MIME type not in allowlist
- `FILE_NOT_FOUND` - File doesn't exist
- `PERMISSION_DENIED` - No access to file
- `AUTH_FAILED` - Invalid token
- `DOWNLOAD_FAILED` - Network or server error
**Security Restrictions:**
Blocked file extensions:
```
.exe, .sh, .bat, .cmd, .com, .msi, .dll, .so, .dylib,
.app, .dmg, .pkg, .deb, .rpm, .apk, .ipa, .jar, .war,
.ear, .py, .rb, .pl, .php, .js, .ts, .vbs, .ps1, .psm1,
.scr, .hta, .bin, .run, .out, .elf
```
Default allowed MIME types:
```
image/*, video/*, audio/*, text/*
application/pdf, application/json, application/xml
application/csv, application/zip, application/gzip, application/tar
```
**Example:**
```typescript
const result = await downloadFile({
cfg: openclawConfig,
fileId: 'file123abc',
destinationPath: '/tmp/report.pdf',
maxSize: 50 * 1024 * 1024 // 50MB
});
if (result.ok) {
console.log(`Downloaded: ${result.filePath}`);
console.log(`Size: ${result.metadata.size} bytes`);
console.log(`Type: ${result.metadata.mimeType}`);
}
```
---
### sendMessage
Send a message to a Mattermost channel.
```typescript
import { sendMessage } from '@lumbridgecorp/openclaw-mattermost';
await sendMessage({
cfg: OpenClawConfig,
channelId: string,
message: string,
rootId?: string,
fileIds?: string[],
props?: Record<string, unknown>,
accountId?: string | null
});
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `cfg` | `OpenClawConfig` | Yes | OpenClaw configuration |
| `channelId` | `string` | Yes | Target channel ID |
| `message` | `string` | Yes | Message content (supports Markdown) |
| `rootId` | `string` | No | Parent post ID for threaded replies |
| `fileIds` | `string[]` | No | Array of uploaded file IDs to attach |
| `props` | `Record<string, unknown>` | No | Additional post properties |
| `accountId` | `string \| null` | No | Specific account to use |
**Example:**
```typescript
await sendMessage({
cfg: openclawConfig,
channelId: 'channel123',
message: 'Hello **World**!', // Markdown supported
rootId: 'parent-post-id' // Thread reply
});
```
---
### addReaction
Add an emoji reaction to a message.
```typescript
import { addReaction } from '@lumbridgecorp/openclaw-mattermost';
await addReaction({
cfg: OpenClawConfig,
postId: string,
emoji: string, // e.g., 'thumbsup', 'white_check_mark'
accountId?: string | null
});
```
**Example:**
```typescript
await addReaction({
cfg: openclawConfig,
postId: 'post123',
emoji: 'thumbsup'
});
```
---
### uploadFile
Upload a file to Mattermost.
```typescript
import { uploadFile } from '@lumbridgecorp/openclaw-mattermost';
const fileInfo = await uploadFile({
cfg: OpenClawConfig,
channelId: string,
buffer: Buffer,
fileName: string,
contentType?: string,
accountId?: string | null
});
```
**Returns:** `MattermostFileInfo` object with `id`, `name`, `mime_type`, `size`
**Example:**
```typescript
import { readFileSync } from 'fs';
const buffer = readFileSync('./document.pdf');
const fileInfo = await uploadFile({
cfg: openclawConfig,
channelId: 'channel123',
buffer,
fileName: 'document.pdf',
contentType: 'application/pdf'
});
// Use the file ID when sending a message
await sendMessage({
cfg: openclawConfig,
channelId: 'channel123',
message: 'Here is the document:',
fileIds: [fileInfo.id]
});
```
---
## Interactive Directives
Interactive directives allow you to embed buttons and dropdowns in messages using a special syntax. These are parsed and converted to Mattermost interactive components.
### Button Directive
Syntax:
```
[[mattermost_buttons: Label1:value1:style1, Label2:value2:style2, ...]]
```
**Format:**
- `Label`: Display text on the button (max 30 chars)
- `value`: Value sent when button is clicked
- `style`: Button style - `default`, `primary`, or `danger` (optional, defaults to `default`)
**Maximum:** 5 buttons per directive
**Example:**
```typescript
const message = `
Please review this request:
[[mattermost_buttons:
Approve:approve_request:primary,
Reject:reject_request:danger,
Review Later:review_later:default
]]
`;
await sendMessage({ cfg: openclawConfig, channelId: 'channel123', message });
```
---
### Select/Dropdown Directive
Syntax:
```
[[mattermost_select: Placeholder | Label1:value1, Label2:value2, ...]]
```
**Format:**
- `Placeholder`: Text shown when nothing selected (optional, defaults to "Choose an option")
- `Label`: Display text for the option
- `value`: Value sent when option is selected
**Maximum:** 100 options per directive
**Example:**
```typescript
const message = `
Select a priority level:
[[mattermost_select:
Select Priority |
Low:low,
Medium:medium,
High:high,
Critical:critical
]]
`;
await sendMessage({ cfg: openclawConfig, channelId: 'channel123', message });
```
---
### Multiple Directives
You can combine multiple directives in a single message:
```typescript
const message = `
Deployment ready for production.
[[mattermost_buttons: Deploy:deploy:primary, Cancel:cancel:danger]]
Select deployment region:
[[mattermost_select: Choose Region | US East:us-east, US West:us-west, EU:eu, APAC:apac]]
`;
```
---
### Handling Interactions
When a user interacts with buttons or selects, Mattermost sends a callback to your configured `interactions.callbackBaseUrl`:
```json
{
"type": "button" | "select",
"value": "the_button_or_option_value",
"user_id": "mattermost_user_id",
"channel_id": "channel_id",
"post_id": "original_post_id",
"context": { ... }
}
```
**Configuration:**
```json
{
"channels": {
"mattermost": {
"interactions": {
"callbackBaseUrl": "https://your-bot.example.com/callbacks",
"allowedSourceIps": ["10.0.0.0/8", "192.168.1.0/24"]
}
}
}
}
```
---
## Configuration Types
### MattermostAccountConfig
```typescript
interface MattermostAccountConfig {
// Core settings
baseUrl?: string;
botToken?: SecretInput;
pat?: SecretInput;
enabled?: boolean;
name?: string;
// Access control
dmPolicy?: 'open' | 'allowlist' | 'pairing';
allowFrom?: Array<string | number>;
groupPolicy?: 'open' | 'allowlist';
groupAllowFrom?: Array<string | number>;
requireMention?: boolean;
// Chat modes
chatmode?: 'oncall' | 'onmessage' | 'onchar';
oncharPrefixes?: string[];
// Message settings
textChunkLimit?: number;
chunkMode?: 'length' | 'newline';
blockStreaming?: boolean;
blockStreamingCoalesce?: BlockStreamingConfig;
replyToMode?: 'off' | 'first' | 'all';
responsePrefix?: string;
// Slash commands
commands?: {
native?: boolean | 'auto';
nativeSkills?: boolean | 'auto';
callbackPath?: string;
callbackUrl?: string;
};
// Interactions
interactions?: {
callbackBaseUrl?: string;
allowedSourceIps?: string[];
};
// Actions
actions?: {
reactions?: boolean;
downloadFile?: boolean;
delete?: boolean;
};
// Security
allowPrivateNetwork?: boolean;
configWrites?: boolean;
dangerouslyAllowNameMatching?: boolean;
// Retry
dmChannelRetry?: {
maxRetries?: number;
initialDelayMs?: number;
maxDelayMs?: number;
timeoutMs?: number;
};
// Group-specific settings
groups?: Record<string, {
requireMention?: boolean;
}>;
// Capabilities
capabilities?: string[] | { interactiveReplies?: boolean };
markdown?: MarkdownConfig;
}
```
### SecretInput
Secret values can be provided in multiple formats:
```typescript
type SecretInput =
| string // Raw value (not recommended for production)
| { $env: string } // Environment variable reference
| { $file: string } // File path containing secret
| { $secretRef: string }; // Secret manager reference
```
**Examples:**
```json
{
"botToken": "${MATTERMOST_BOT_TOKEN}",
"botToken": { "$env": "MATTERMOST_BOT_TOKEN" },
"botToken": { "$file": "/run/secrets/bot_token" }
}
```
---
## Error Handling
### Error Classes
All errors extend `MattermostError` with structured information:
```typescript
class MattermostError extends Error {
readonly code: ErrorCode;
readonly context: ErrorContext;
readonly retryable: boolean;
readonly userMessage: string;
readonly retryAfterMs?: number;
toJSON(): Record<string, unknown>;
toLogString(): string;
}
```
### Error Codes
| Code | Description | Retryable |
|------|-------------|-----------|
| `MATTERMOST_API_ERROR` | General API error | Depends on status |
| `MATTERMOST_API_TIMEOUT` | Request timeout | Yes |
| `MATTERMOST_API_RATE_LIMIT` | Rate limited | Yes |
| `AUTHENTICATION_FAILED` | Invalid credentials | No |
| `TOKEN_INVALID` | Token revoked | No |
| `TOKEN_EXPIRED` | Token expired | No |
| `PERMISSION_DENIED` | Access denied | No |
| `POST_EDIT_DENIED` | Cannot edit post | No |
| `CHANNEL_ACCESS_DENIED` | Channel access denied | No |
| `RESOURCE_NOT_FOUND` | Resource not found | No |
| `VALIDATION_ERROR` | Invalid input | No |
| `CONFIGURATION_ERROR` | Bad configuration | No |
| `MISSING_BOT_TOKEN` | No bot token | No |
| `MISSING_BASE_URL` | No base URL | No |
| `NETWORK_ERROR` | Network failure | Yes |
| `CONNECTION_ERROR` | Cannot connect | Yes |
| `TIMEOUT_ERROR` | Operation timed out | Yes |
| `FILE_TOO_LARGE` | File exceeds limit | No |
| `BLOCKED_FILE_TYPE` | Executable file | No |
| `DOWNLOAD_FAILED` | Download error | Yes |
### Using Error Boundaries
```typescript
import { globalErrorBoundary, MattermostError } from '@lumbridgecorp/openclaw-mattermost';
// Register error handler
const unsubscribe = globalErrorBoundary.onError((error) => {
if (error instanceof MattermostError) {
console.error(`[${error.code}] ${error.toLogString()}`);
if (error.retryable) {
console.log(`Retry after: ${error.retryAfterMs}ms`);
}
}
});
// Wrap operations
const safeOperation = globalErrorBoundary.wrap(
riskyOperation,
'my-operation',
{ accountId: 'default' }
);
// Later: unsubscribe
disunsubscribe();
```
### Retry Configuration
```typescript
import { withRetry } from '@lumbridgecorp/openclaw-mattermost';
const result = await withRetry(
'my-operation',
async () => {
// Your operation here
return await someApiCall();
},
{
maxRetries: 5,
initialDelayMs: 1000,
maxDelayMs: 30000,
timeoutMs: 60000,
onRetry: ({ attempt, maxRetries, delayMs, error }) => {
console.log(`Retry ${attempt}/${maxRetries} after ${delayMs}ms: ${error.message}`);
}
}
);
```
---
## Security Audit
### collectMattermostSecurityAuditFindings
Run a comprehensive security audit on your configuration:
```typescript
import { collectMattermostSecurityAuditFindings } from '@lumbridgecorp/openclaw-mattermost';
const findings = await collectMattermostSecurityAuditFindings({
cfg: OpenClawConfig,
accountId?: string | null,
account: ResolvedMattermostAccount
});
```
**Returns:** Array of `SecurityAuditFinding`:
```typescript
type SecurityAuditFinding = {
checkId: string; // Unique check identifier
severity: 'info' | 'warn' | 'critical';
title: string;
detail: string;
remediation?: string;
};
```
### Audit Checks
| Check ID | Severity | Description |
|----------|----------|-------------|
| `channels.mattermost.pat.hardcoded_token` | Critical | Bot token hardcoded in config |
| `channels.mattermost.pat.short_token` | Warn | Token unusually short |
| `channels.mattermost.pat.missing_token` | Critical | No bot token configured |
| `channels.mattermost.https.insecure_url` | Critical | HTTP instead of HTTPS |
| `channels.mattermost.https.missing_url` | Critical | No base URL configured |
| `channels.mattermost.input.insecure_callback` | Critical | Callback URL uses HTTP |
| `channels.mattermost.input.no_source_ip_restriction` | Warn | No IP allowlist on callbacks |
| `channels.mattermost.network.private_access_enabled` | Warn | Private network access enabled |
| `channels.mattermost.commands.access_groups_disabled` | Critical | Commands bypass access groups |
| `channels.mattermost.commands.no_allowlist` | Warn | No allowlist for slash commands |
| `channels.mattermost.config.legacy_dm_policy` | Info | Legacy dm.policy config found |
| `channels.mattermost.config.legacy_allow_from` | Info | Legacy dm.allowFrom config found |
| `channels.mattermost.allowlist.mutable_entries` | Warn | Non-ID entries in allowlist |
| `channels.mattermost.actions.download_file_enabled` | Info | File download enabled |
| `channels.mattermost.actions.delete_enabled` | Info | Message delete enabled |
| `channels.mattermost.groups.open_policy` | Warn | Open group policy |
| `channels.mattermost.dm.open_policy` | Warn | Open DM policy |
| `channels.mattermost.dm.pairing_mode` | Info | DM pairing mode active |
**Example:**
```typescript
import {
collectMattermostSecurityAuditFindings,
collectAllMattermostSecurityAuditFindings
} from '@lumbridgecorp/openclaw-mattermost';
// Audit single account
const findings = await collectMattermostSecurityAuditFindings({
cfg: openclawConfig,
accountId: 'default',
account: resolvedAccount
});
// Audit all accounts
const allFindings = await collectAllMattermostSecurityAuditFindings({
cfg: openclawConfig,
listAccounts: (cfg) => resolveAllAccounts(cfg)
});
// Display findings by severity
const critical = findings.filter(f => f.severity === 'critical');
const warnings = findings.filter(f => f.severity === 'warn');
console.log(`Critical issues: ${critical.length}`);
for (const finding of critical) {
console.log(`\n[!] ${finding.title}`);
console.log(` ${finding.detail}`);
console.log(` Fix: ${finding.remediation}`);
}
```
---
## Client API
### createMattermostClient
Create a low-level Mattermost API client:
```typescript
import { createMattermostClient } from '@lumbridgecorp/openclaw-mattermost';
const client = createMattermostClient({
baseUrl: string;
botToken: string;
pat?: string;
fetchImpl?: MattermostFetch;
allowPrivateNetwork?: boolean;
accountId?: string;
});
```
**Client Methods:**
| Method | Description |
|--------|-------------|
| `request<T>(path, init?)` | Make authenticated API request |
| `fetchImpl(input, init?)` | Low-level fetch with guards |
**Request Examples:**
```typescript
// Get current user
const me = await client.request<MattermostUser>('/users/me');
// Get a channel
const channel = await client.request<MattermostChannel>(`/channels/${channelId}`);
// Create a post
const post = await client.request<MattermostPost>('/posts', {
method: 'POST',
body: JSON.stringify({
channel_id: channelId,
message: 'Hello World'
})
});
```
### Helper Functions
```typescript
// User operations
fetchMattermostMe(client) -> MattermostUser
fetchMattermostUser(client, userId) -> MattermostUser
fetchMattermostUserByUsername(client, username) -> MattermostUser
// Channel operations
fetchMattermostChannel(client, channelId) -> MattermostChannel
fetchMattermostChannelByName(client, teamId, channelName) -> MattermostChannel
createMattermostDirectChannel(client, userIds) -> MattermostChannel
createMattermostDirectChannelWithRetry(client, userIds, options) -> MattermostChannel
// Post operations
createMattermostPost(client, params) -> MattermostPost
updateMattermostPost(client, postId, params) -> MattermostPost
// File operations
uploadMattermostFile(client, params) -> MattermostFileInfo
// Other
sendMattermostTyping(client, { channelId, parentId }) -> void
fetchMattermostUserTeams(client, userId) -> MattermostTeam[]
```
---
## Type Exports
```typescript
import {
// Config types
MattermostAccountConfig,
MattermostConfig,
SecretInput,
// Client types
MattermostClient,
MattermostFetch,
MattermostUser,
MattermostChannel,
MattermostPost,
MattermostFileInfo,
MattermostTeam,
// Action types
EditMessageParams,
EditMessageResult,
DeleteMessageParams,
DeleteMessageResult,
DownloadFileParams,
DownloadFileResult,
FileMetadata,
// Error types
MattermostError,
MattermostErrorOptions,
ErrorContext,
ErrorCode,
MattermostAPIError,
AuthenticationError,
ValidationError,
PermissionError,
RateLimitError,
ResourceNotFoundError,
NetworkError,
ConfigurationError,
// Security types
SecurityAuditFinding,
// Retry types
RetryConfig,
RetryAttemptInfo
} from '@lumbridgecorp/openclaw-mattermost';
```
+562
View File
@@ -0,0 +1,562 @@
# Migration Guide
Guide for migrating from the stock Mattermost extension to the Enhanced Mattermost Extension.
## Table of Contents
- [Overview](#overview)
- [Before You Begin](#before-you-begin)
- [Configuration Migration](#configuration-migration)
- [Breaking Changes](#breaking-changes)
- [Feature Upgrades](#feature-upgrades)
- [Code Migration](#code-migration)
- [Verification](#verification)
- [Rollback Plan](#rollback-plan)
## Overview
The Enhanced Mattermost Extension is a drop-in replacement for the stock extension with additional features and improved security. Most existing configurations will work without changes, but there are some differences to be aware of.
### Key Differences
| Aspect | Stock Extension | Enhanced Extension |
|--------|--------------|-------------------|
| Package name | `@modelcontextprotocol/mattermost` | `@lumbridgecorp/openclaw-mattermost` |
| Token support | Bot token only | Bot token + PAT |
| Message operations | Send only | Send, edit, delete |
| File operations | None | Download with security |
| Interactive components | None | Buttons and selects |
| Security audit | None | Built-in |
| Error handling | Basic | Structured with retry |
| Configuration | Simple | Extended but backward compatible |
## Before You Begin
### Prerequisites
1. **Backup your configuration** before making any changes
2. **Review current usage** of Mattermost features in your code
3. **Check Node.js version** - requires Node.js 22+ (up from 18+)
4. **Plan for downtime** during the migration window
### Compatibility Check
Run this check to see if your current configuration is compatible:
```bash
# Install the enhanced extension alongside your current one
npm install @lumbridgecorp/openclaw-mattermost
# Run the security audit to check configuration
npx openclaw doctor --channel=mattermost
```
## Configuration Migration
### Simple Migration (No Code Changes)
If you're using basic features, simply update your package.json:
```diff
{
"dependencies": {
- "@modelcontextprotocol/mattermost": "^0.x.x"
+ "@lumbridgecorp/openclaw-mattermost": "^1.0.0"
}
}
```
Your existing configuration will continue to work:
```json
{
"channels": {
"mattermost": {
"baseUrl": "https://chat.example.com",
"botToken": "${MATTERMOST_BOT_TOKEN}",
"team": "myteam"
}
}
}
```
### Enhanced Configuration (Recommended)
Take advantage of new security features by updating your configuration:
#### Before (Stock Extension)
```json
{
"channels": {
"mattermost": {
"url": "https://chat.example.com",
"token": "${MATTERMOST_TOKEN}",
"team": "engineering"
}
}
}
```
#### After (Enhanced Extension)
```json
{
"channels": {
"mattermost": {
"baseUrl": "https://chat.example.com",
"botToken": "${MATTERMOST_BOT_TOKEN}",
"dmPolicy": "pairing",
"allowFrom": ["user1-id", "user2-id"],
"groupPolicy": "allowlist",
"groupAllowFrom": ["user1-id", "user2-id"],
"interactions": {
"callbackBaseUrl": "https://bot.example.com/callbacks",
"allowedSourceIps": ["10.0.0.0/8"]
},
"actions": {
"reactions": true,
"downloadFile": true,
"delete": true
}
}
}
}
```
### Multi-Account Configuration
The enhanced extension supports multiple Mattermost accounts:
```json
{
"channels": {
"mattermost": {
"accounts": {
"production": {
"baseUrl": "https://chat.company.com",
"botToken": "${PROD_BOT_TOKEN}",
"dmPolicy": "allowlist"
},
"staging": {
"baseUrl": "https://chat-staging.company.com",
"botToken": "${STAGING_BOT_TOKEN}",
"dmPolicy": "open"
}
},
"defaultAccount": "production"
}
}
}
```
## Breaking Changes
### 1. Import Path Changes
**Before:**
```typescript
import { sendMessage } from '@modelcontextprotocol/mattermost';
```
**After:**
```typescript
import { sendMessage } from '@lumbridgecorp/openclaw-mattermost';
```
### 2. Configuration Property Names
Some property names have changed for clarity:
| Old Name | New Name | Notes |
|----------|----------|-------|
| `url` | `baseUrl` | Both work, `baseUrl` preferred |
| `token` | `botToken` | Both work, `botToken` preferred |
### 3. Environment Variable Names
If you used the stock extension's environment variables:
| Old Variable | New Variable | Notes |
|--------------|--------------|-------|
| `MATTERMOST_URL` | `MATTERMOST_URL` | No change |
| `MATTERMOST_TOKEN` | `MATTERMOST_BOT_TOKEN` | Recommended change |
| - | `MATTERMOST_PAT` | New optional PAT variable |
Both old and new variable names are supported for backward compatibility.
### 4. Type Changes
Some TypeScript types have been renamed or extended:
**Before:**
```typescript
import { MattermostConfig } from '@modelcontextprotocol/mattermost';
```
**After:**
```typescript
import { MattermostAccountConfig, MattermostConfig } from '@lumbridgecorp/openclaw-mattermost';
```
## Feature Upgrades
### Adding PAT Support
**Step 1:** Create a Personal Access Token in Mattermost
1. Log in to Mattermost as the bot owner
2. Go to Account Settings > Security > Personal Access Tokens
3. Create a new token
**Step 2:** Add PAT to your configuration
```bash
export MATTERMOST_PAT="your-pat-here"
```
```json
{
"channels": {
"mattermost": {
"baseUrl": "https://chat.example.com",
"botToken": "${MATTERMOST_BOT_TOKEN}",
"pat": "${MATTERMOST_PAT}"
}
}
}
```
### Enabling Interactive Components
Add interaction configuration to enable buttons and selects:
```json
{
"channels": {
"mattermost": {
"baseUrl": "https://chat.example.com",
"botToken": "${MATTERMOST_BOT_TOKEN}",
"interactions": {
"callbackBaseUrl": "https://your-bot.example.com/callbacks",
"allowedSourceIps": ["10.0.0.0/8"]
}
}
}
}
```
### Enabling File Downloads
File downloads are enabled by default but can be controlled:
```json
{
"channels": {
"mattermost": {
"actions": {
"downloadFile": true
}
}
}
}
```
## Code Migration
### Sending Messages (No Change Required)
```typescript
// Both versions work the same
import { sendMessage } from '@lumbridgecorp/openclaw-mattermost';
await sendMessage({
cfg: openclawConfig,
channelId: 'channel123',
message: 'Hello World'
});
```
### Adding Reactions
**Before:** Limited or no support
**After:** Full support with type safety
```typescript
import { addReaction } from '@lumbridgecorp/openclaw-mattermost';
await addReaction({
cfg: openclawConfig,
postId: 'post123',
emoji: 'thumbsup'
});
```
### Editing Messages (New Feature)
```typescript
import { editMessage } from '@lumbridgecorp/openclaw-mattermost';
const result = await editMessage({
cfg: openclawConfig,
postId: 'post123',
channelId: 'channel123',
message: 'Updated content'
});
if (!result.ok) {
console.error('Edit failed:', result.error);
}
```
### Interactive Messages (New Feature)
```typescript
const message = `
Please confirm:
[[mattermost_buttons: Yes:confirm:primary, No:cancel:danger]]
`;
await sendMessage({ cfg: openclawConfig, channelId: 'channel123', message });
```
### File Downloads (New Feature)
```typescript
import { downloadFile } from '@lumbridgecorp/openclaw-mattermost';
const result = await downloadFile({
cfg: openclawConfig,
fileId: 'file123',
destinationPath: '/tmp/download.pdf',
maxSize: 50 * 1024 * 1024
});
if (result.ok) {
console.log('Downloaded:', result.filePath);
}
```
### Error Handling Improvements
**Before:** Basic error handling
```typescript
try {
await sendMessage({ cfg, channelId, message });
} catch (error) {
console.error('Failed:', error);
}
```
**After:** Structured error handling with retry
```typescript
import { MattermostError, isRetryableError } from '@lumbridgecorp/openclaw-mattermost';
try {
await sendMessage({ cfg, channelId, message });
} catch (error) {
if (error instanceof MattermostError) {
console.error(`[${error.code}] ${error.userMessage}`);
if (error.retryable) {
console.log(`Can retry after ${error.retryAfterMs}ms`);
}
if (error.code === 'AUTHENTICATION_FAILED') {
// Refresh token and retry
}
}
}
```
## Verification
After migration, verify everything works:
### 1. Run the Security Audit
```typescript
import { collectMattermostSecurityAuditFindings } from '@lumbridgecorp/openclaw-mattermost';
const findings = await collectMattermostSecurityAuditFindings({
cfg: openclawConfig,
accountId: 'default',
account: resolvedAccount
});
const critical = findings.filter(f => f.severity === 'critical');
if (critical.length > 0) {
console.error('Critical security issues found:', critical);
process.exit(1);
}
```
### 2. Test Basic Operations
```typescript
// Test connection
const me = await fetchMattermostMe(client);
console.log('Connected as:', me.username);
// Test message sending
const post = await sendMessage({ cfg, channelId: testChannel, message: 'Test' });
console.log('Sent message:', post.id);
// Test message editing
const editResult = await editMessage({
cfg,
postId: post.id,
channelId: testChannel,
message: 'Updated test'
});
console.log('Edit result:', editResult.ok);
// Test message deletion
const deleteResult = await deleteMessage({
cfg,
postId: post.id,
channelId: testChannel
});
console.log('Delete result:', deleteResult.ok);
```
### 3. Checklist
- [ ] Messages send successfully
- [ ] Messages can be edited
- [ ] Messages can be deleted
- [ ] Reactions can be added
- [ ] Files can be downloaded (if enabled)
- [ ] Interactive components render (if configured)
- [ ] No critical security audit findings
- [ ] Error handling works as expected
- [ ] Rate limiting is handled gracefully
## Rollback Plan
If you need to rollback to the stock extension:
### Step 1: Revert Package
```bash
npm uninstall @lumbridgecorp/openclaw-mattermost
npm install @modelcontextprotocol/mattermost
```
### Step 2: Revert Configuration
Remove any enhanced-specific configuration:
```diff
{
"channels": {
"mattermost": {
"url": "https://chat.example.com",
- "baseUrl": "https://chat.example.com",
- "botToken": "${MATTERMOST_BOT_TOKEN}",
- "pat": "${MATTERMOST_PAT}",
- "dmPolicy": "pairing",
+ "token": "${MATTERMOST_TOKEN}",
- "interactions": { ... },
- "actions": { ... }
}
}
}
```
### Step 3: Revert Code Changes
Change imports back:
```diff
- import { sendMessage } from '@lumbridgecorp/openclaw-mattermost';
+ import { sendMessage } from '@modelcontextprotocol/mattermost';
```
Remove any code using enhanced-only features:
- Message editing
- Message deletion
- File downloads
- Interactive components
- Security audit calls
## Troubleshooting Migration Issues
### Issue: "Cannot find module"
**Solution:** Ensure the package is installed:
```bash
npm install @lumbridgecorp/openclaw-mattermost
```
### Issue: "Configuration validation failed"
**Solution:** Check for legacy configuration properties:
```typescript
import { MattermostConfigSchema } from '@lumbridgecorp/openclaw-mattermost';
const result = MattermostConfigSchema.safeParse(yourConfig);
if (!result.success) {
console.error('Config errors:', result.error.errors);
}
```
### Issue: "Authentication failed"
**Solution:** Verify token format and permissions:
```bash
# Test token with curl
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://chat.example.com/api/v4/users/me
```
### Issue: "Interactive components not working"
**Solution:** Check interaction callback configuration:
1. Verify `interactions.callbackBaseUrl` is HTTPS
2. Ensure `allowedSourceIps` includes your Mattermost server
3. Check that the callback endpoint is accessible
4. Verify Mattermost can reach your callback URL
### Issue: "File download blocked"
**Solution:** Check file restrictions:
1. Verify file is not an executable type
2. Check file size against `maxSize` limit
3. Ensure bot has file access permissions
4. Check MIME type is in allowlist
## Getting Help
If you encounter issues during migration:
1. **Check the security audit output** for configuration issues
2. **Review the [API Reference](API.md)** for correct usage
3. **Search [GitHub Issues](https://github.com/lumbridgecorp/openclaw-extentions-mattermost/issues)** for similar problems
4. **Create a new issue** with:
- Your migration step
- Error messages
- Configuration (with secrets redacted)
- Expected vs actual behavior
## Migration Summary
| Task | Effort | Notes |
|------|--------|-------|
| Package update | 5 min | Simple npm/pnpm/yarn command |
| Basic config migration | 10 min | Property name changes |
| PAT setup (optional) | 15 min | Create token, update config |
| Interactive components | 30 min | Add interaction config |
| Code migration | 1-2 hours | Update imports, add features |
| Testing | 30 min | Verify all operations work |
| Security audit | 15 min | Fix any findings |
**Total estimated time: 2-4 hours** for a complete migration with all features.