This guide walks through setting up the Mattermost connector for OpenCode Chat Bridge.
The Mattermost connector uses the REST API v4 and WebSocket for real-time messaging. It requires zero external npm dependencies -- only native fetch and WebSocket (built into Bun and Node.js 22+). This makes it lightweight and easy to deploy on any Mattermost instance (self-hosted or cloud).
How it works:
- The bot authenticates with a bot access token
- A WebSocket connection receives real-time message events
- Messages matching the trigger prefix or @mentions are forwarded to OpenCode
- Responses are sent back via the REST API
- A Mattermost server (self-hosted or cloud) with admin access
bunruntime installed- OpenCode installed and authenticated (
opencode --version)
- Log in to your Mattermost server as an admin
- Go to Integrations > Bot Accounts (not System Console > Bot Accounts, which is just settings)
- Click Add Bot Account
- Fill in:
- Username: your bot name (e.g.,
my-opencode-bot) - Display Name: friendly name shown in conversations
- Description: optional
- Role: Member (sufficient for most use cases)
- Username: your bot name (e.g.,
- Click Create Bot Account
- Copy the Access Token -- this is your
MATTERMOST_TOKEN
Important: The token is only shown once. If you lose it, you can regenerate it from the bot account settings.
Bots must be added to a team before they can see channels or receive messages.
- Go to the team you want the bot to join
- Open Team Settings > Members
- Add the bot user to the team
Alternatively, use the Mattermost API:
curl -X POST "https://mattermost.example.com/api/v4/teams/TEAM_ID/members" \
-H "Authorization: Bearer ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"team_id": "TEAM_ID", "user_id": "BOT_USER_ID"}'To find the team ID and bot user ID:
# Get team ID by name
curl -s "https://mattermost.example.com/api/v4/teams/name/myteam" \
-H "Authorization: Bearer ADMIN_TOKEN" | jq .id
# Get bot user ID
curl -s "https://mattermost.example.com/api/v4/users/username/my-opencode-bot" \
-H "Authorization: Bearer ADMIN_TOKEN" | jq .idAdd credentials to your .env file:
MATTERMOST_URL=https://mattermost.example.com
MATTERMOST_TOKEN=your_bot_access_token
# Team name/slug (optional -- auto-detected if bot is in one team)
# MATTERMOST_TEAM=myteam
# Optional: override trigger prefix (default: !oc)
# MATTERMOST_TRIGGER=!ocAnd configure the connector in chat-bridge.json:
{
"mattermost": {
"enabled": true,
"url": "{env:MATTERMOST_URL}",
"token": "{env:MATTERMOST_TOKEN}",
"teamName": "",
"respondToMentions": true,
"ignoreChannels": [],
"ignoreUsers": []
}
}| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
Enable the Mattermost connector |
url |
string | "" |
Mattermost server URL |
token |
string | "" |
Bot access token |
teamName |
string | "" |
Team slug (empty = auto-detect) |
respondToMentions |
boolean | true |
Respond when @mentioned (in addition to trigger) |
ignoreChannels |
string[] | [] |
Channel IDs to ignore |
ignoreUsers |
string[] | [] |
User IDs to ignore |
bun connectors/mattermost.tsYou should see:
[MATTERMOST] Starting...
Server: https://mattermost.example.com
Trigger: !oc
Bot name: OpenCode Bot
Bot user: @my-opencode-bot (abc123def456)
Responds to: trigger "!oc" and @my-opencode-bot mentions
[MATTERMOST] WebSocket authenticated
[MATTERMOST] Started! Listening for messages...
Go to any channel in the configured team and send:
!oc hello
Or @mention the bot:
@my-opencode-bot hello
Messages in channels must start with the trigger (default: !oc) or @mention the bot:
!oc what time is it?
!oc search the web for climate change
@my-opencode-bot who are you?
In DMs with the bot, no trigger or @mention is needed:
what time is it?
search for map projections
Every conversation happens inside a Mattermost thread:
- Trigger or @mention in a channel starts a new thread with its own session
- Replies within the thread are forwarded automatically -- no need to re-trigger
- Different threads have completely separate sessions and context
- Sessions expire after inactivity (configurable via
SESSION_RETENTION_MINS)
Configure in chat-bridge.json:
{
"mattermost": {
"threadIsolation": true
}
}Set to false for per-channel sessions (old behavior).
| Command | Description |
|---|---|
!oc /help |
Show available commands (including OpenCode commands) |
!oc /status |
Show session info and directory |
!oc /clear |
Reset conversation session |
!oc /reset |
Same as /clear |
OpenCode's built-in commands are discovered and forwarded automatically:
!oc /init # Initialize context with codebase summary
!oc /compact # Compress conversation history
!oc /review # Review recent changes
These appear in /help and are passed directly to OpenCode.
The bot shows what tools are being used during processing:
> command=free -h | grep Mem, description=Get memory usage [bash]
> Getting time in Europe/Madrid [time_get_current_time]
Each channel and DM has its own conversation session. Users can reference previous
messages within the same channel. Use /clear to reset.
When respondToMentions is enabled (default), the bot responds to both the trigger
prefix and @mentions. The bot username is resolved from the API on startup:
!oc what time is it? # Trigger prefix
@my-opencode-bot what time is it? # @mention
Disable with "respondToMentions": false in chat-bridge.json if you only want
trigger-based activation.
When tools produce image files (e.g., from a document library MCP), the connector uploads them to Mattermost as file attachments.
Mattermost has a 16,383 character limit per post. Long responses are automatically split at newline boundaries into multiple messages.
When streamTools includes a tool name (default: ["bash"]), the tool's output
is streamed to chat in real-time during execution. This is useful for long-running
commands where you want to see progress.
For production, create a systemd service:
[Unit]
Description=OpenCode Mattermost Bridge
After=network.target
[Service]
Type=simple
User=youruser
Group=youruser
WorkingDirectory=/home/youruser/opencode-chat-bridge
Environment=PATH=/home/youruser/.bun/bin:/home/youruser/.opencode/bin:/usr/local/bin:/usr/bin:/bin
Environment=HOME=/home/youruser
ExecStart=/home/youruser/.bun/bin/bun connectors/mattermost.ts
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=opencode-mattermost
[Install]
WantedBy=multi-user.targetNote: Adjust paths for your bun and opencode installations.
sudo systemctl daemon-reload
sudo systemctl enable opencode-mattermost
sudo systemctl start opencode-mattermost
# Check status
sudo systemctl status opencode-mattermost
# View logs
journalctl -u opencode-mattermost -fMattermost Server
| (WebSocket wss://server/api/v4/websocket)
v
Mattermost Connector (connectors/mattermost.ts)
| (ACP Protocol - JSON-RPC/stdio)
v
OpenCode
| (LLM API)
v
AI Response
| (REST API POST /api/v4/posts)
v
Mattermost Server
The connector maintains one ACP session (OpenCode process) per channel, allowing conversation continuity within each channel.
- Keep
MATTERMOST_TOKENsecret -- never commit.envto git - Use a dedicated bot account, not a personal access token
- Store tokens in a secrets manager (e.g., HashiCorp Vault) for production
- The bot only sees channels in teams it has been added to
- Use
ignoreChannelsto exclude specific channels - Use
ignoreUsersto block specific users from interacting
The bot uses the chat-bridge agent with restricted permissions defined in
opencode.json. Even if a malicious prompt tricks the model, OpenCode blocks
the action at the execution level.
Built-in rate limiting prevents spam (configurable, default: 5 second cooldown per user).
- Check the bot is added to the team (not just created)
- Verify the message starts with the trigger (
!oc) or @mentions the bot - Check that
respondToMentionsistrueif using @mentions - Look at connector logs:
journalctl -u opencode-mattermost -f - Test OpenCode directly:
bun src/cli.ts "test"
- Regenerate the bot access token in Integrations > Bot Accounts
- Make sure you are using the bot token, not a personal access token
- Check the token hasn't been revoked by an admin
The connector automatically reconnects with exponential backoff (up to 10 attempts):
[MATTERMOST] WebSocket closed: 1006
[MATTERMOST] Reconnecting in 3000ms (attempt 1/10)...
[MATTERMOST] Reconnected successfully
If reconnection fails repeatedly:
- Check server logs for rate limiting
- Verify the server allows WebSocket connections
- Check network/firewall rules
- Verify WebSocket is connected (check logs for "WebSocket authenticated")
- Ensure the bot has permissions in the channel
- Check if the message is from a user in the
ignoreUserslist - Check if the channel is in the
ignoreChannelslist
This is expected behavior -- the opencode.json restricts what tools the AI can use.
For example, if the AI tries to read files but read: deny is set, OpenCode blocks
it and returns an error to the model. See Security for details.
- Review Security documentation for permission details
- See Architecture for system design
- See Configuration for all config options
- Check Contributing to help improve the connector