A Node.js 20 / TypeScript MCP server over stdio that wraps the official
@mattermost/client
Client4 (REST v4). It authenticates to a self-hosted Mattermost instance and exposes
34 tools across messaging, channels/teams, users/presence, and file operations. Safe-by-default
via a configurable guardrail layer; all output goes to stdout (the MCP channel) and all logging goes
to stderr.
Self-hosted only. Mattermost Cloud does not expose the REST v4 endpoints used here; you need a self-managed Mattermost server (any recent version supporting REST v4).
- 3 authentication modes — Personal Access Token (default), username/password (+MFA), OAuth2 (authorization-code + PKCE S256 with transparent token refresh).
- 34 tools across 4 domains — messaging (13), channels & teams (12), users & presence (6), files (3).
- Safe by default — read-only mode (
MM_READ_ONLY), destructive-action gate (MM_ALLOW_DESTRUCTIVE), team/channel allowlists, and per-callconfirm: truerequirement for destructive tools. - Resilience — configurable request timeout, retry with exponential back-off + jitter on
429/5xx/ network errors. - Strict validation — every tool input is validated with a Zod schema before any API call. Mattermost errors are surfaced verbatim (status code + message).
- Node.js ≥ 20
- A self-hosted Mattermost server with a user account and credentials (PAT, login/password, or an OAuth2 application)
npm i -g @bryanberger/mattermost-mcp
mattermost-mcp login # interactive: server URL + auth, saved to the config dir
mattermost-mcp status # verify: prints the authenticated identitylogin validates the credentials against the server and writes them (0600) under
$XDG_CONFIG_HOME/mattermost-mcp (or ~/.config/mattermost-mcp). The MCP server then picks them up
automatically — no env vars required. See CLI and Authenticate with login.
npm install
npm run build
# Binary is now at dist/index.js (also available as mattermost-mcp via the bin field)The mattermost-mcp binary is both the MCP server and a small management CLI:
| Command | Description |
|---|---|
mattermost-mcp |
Start the MCP server on stdio (default — this is what MCP clients run). |
mattermost-mcp login |
Interactive auth wizard; validates and saves credentials. |
mattermost-mcp login --gitlab |
Browser SSO login (GitLab/SAML) — no admin or PAT required. |
mattermost-mcp status |
Authenticate with the resolved config and print the current identity. |
mattermost-mcp logout |
Remove saved credentials (and the matching OAuth token cache). |
mattermost-mcp --help |
Usage. --version prints the version. |
$ mattermost-mcp login
Mattermost server URL (e.g. https://mm.example.com): https://mattermost.example.com
Authentication mode (↑/↓ to move, Enter to select):
› pat paste a Personal Access Token
password username/email + password (+ MFA)
oauth2 OAuth2 app + browser consent
gitlab / SSO browser login via GitLab/SAML — no admin
Personal Access Token: ********
✓ Logged in as @alice on https://mattermost.example.com
Saved to ~/.config/mattermost-mcp/credentials.json (mode: pat, 0600)
The mode picker is keyboard-navigable (arrow keys / j·k, digits to jump, Enter to confirm). When
input is not a TTY it falls back to a numbered prompt.
- pat — saves the token as-is.
- password — exchanges your password for a session token and saves that (the password is
never written to disk); re-run
loginwhen the session expires. - oauth2 — runs the browser consent flow and caches the OAuth tokens (refreshed transparently).
- gitlab / SSO — see below; also selectable directly with
mattermost-mcp login --gitlab.
If your server's only login is an external IdP (e.g. GitLab) and you are not an admin, both PATs and OAuth2 apps may be unavailable — they require server settings only an admin can enable. Use the browser SSO login instead:
$ mattermost-mcp login --gitlab
Mattermost server URL: https://mattermost.example.com
Opening a browser window. Complete the GitLab (or other SSO) login there.
✓ Logged in as @alice on https://mattermost.example.com
Saved to ~/.config/mattermost-mcp/credentials.json (mode: pat — browser SSO session token, 0600)
It opens a browser at {server}/login, waits while you complete the SSO login in that window, then
reads the resulting MMAUTHTOKEN session cookie and saves it as a token. The flag is
provider-agnostic (--sso is an alias), and gitlab / SSO is also offered in the login menu.
- Uses your default browser when it is Chromium-based (Chrome, Chromium, Brave, Edge, Opera,
Vivaldi, Arc, Dia…); otherwise the first installed Chromium engine it finds. Override with
MM_CHROME_PATH=/path/to/browser(puppeteer-core can only drive Chromium engines — not Safari or Firefox). - The captured token is a session token — it expires with the server's SSO session length. Re-run
mattermost-mcp login --gitlabwhen calls start returning401. - Cleanest long-term fix: ask an admin to enable Personal Access Tokens, then use
login(pat).
Credentials come from two sources, in order of precedence:
- Environment variables (
MM_*) — always win; ideal for CI and theexamples/MCP client configs. - Saved
logincredentials — the baseline when the matching env vars are absent.
In development you can place env vars in a .env file in the project root and export them before
running; in production pass them directly to the process. A fully annotated .env.example ships in
the repository. Every setting below is also configurable via env.
| Variable | Required | Default | Description |
|---|---|---|---|
MM_URL |
yes | — | Server root URL, no trailing slash, no /api/v4 (e.g. https://mattermost.example.com). |
| Variable | Required | Default | Description |
|---|---|---|---|
MM_AUTH_MODE |
no | pat |
One of pat | password | oauth2. |
MM_TOKEN |
when pat |
— | Personal Access Token (Bearer; no expiry). Enable in System Console → Integrations → Personal Access Tokens. |
MM_LOGIN_ID |
when password |
— | Username or email. |
MM_PASSWORD |
when password |
— | Password. |
MM_MFA_TOKEN |
no | — | MFA one-time code (only if the account enforces MFA). |
MM_CLIENT_ID |
when oauth2 |
— | OAuth2 app client id (register in System Console → Integrations → OAuth 2.0 Applications). |
MM_CLIENT_SECRET |
when oauth2 |
— | OAuth2 app client secret. |
MM_OAUTH_REDIRECT |
no | http://127.0.0.1:7000/callback |
Loopback redirect URI (must start with http:// or https://). |
| Variable | Required | Default | Description |
|---|---|---|---|
MM_READ_ONLY |
no | false |
true disables every write/destructive tool (they hard-refuse at call time; still listed in tools/list). |
MM_ALLOW_DESTRUCTIVE |
no | false |
Gates the 💥 destructive tools; they also require confirm: true per call. |
MM_TEAM_ALLOWLIST |
no | (unrestricted) | CSV of team ids; actions targeting teams outside this list are refused. |
MM_CHANNEL_ALLOWLIST |
no | (unrestricted) | CSV of channel ids; actions targeting channels outside this list are refused. |
MM_MAX_MESSAGE_LEN |
no | 16383 |
Reject messages longer than this (Unicode code points) before sending. |
| Variable | Required | Default | Description |
|---|---|---|---|
MM_REQUEST_TIMEOUT_MS |
no | 30000 |
Per-request timeout in ms (0 disables). |
MM_MAX_RETRIES |
no | 3 |
Retries on 429 / 5xx / network errors with exponential back-off + jitter. |
MM_RETRY_BASE_MS |
no | 500 |
Back-off base delay in ms. |
Create a Personal Access Token in System Console → Integrations → Personal Access Tokens, then:
MM_AUTH_MODE=pat
MM_TOKEN=your-personal-access-tokenNo expiry; revocable at any time; simplest setup.
MM_AUTH_MODE=password
MM_LOGIN_ID=your-username-or-email
MM_PASSWORD=your-password
# MM_MFA_TOKEN=123456 # only if the account enforces MFAThe server calls POST /users/login, captures the session token from the Token response header,
and automatically re-authenticates on a 401.
Register a confidential OAuth2 application in System Console → Integrations → OAuth 2.0
Applications with a loopback redirect URI (http://127.0.0.1:7000/callback by default), then:
MM_AUTH_MODE=oauth2
MM_CLIENT_ID=your-client-id
MM_CLIENT_SECRET=your-client-secret
# MM_OAUTH_REDIRECT=http://127.0.0.1:7000/callback # optional overrideOn first run the server opens a browser for the authorization-code + PKCE S256 flow. Tokens are
cached under $XDG_CONFIG_HOME/mattermost-mcp (or ~/.config/mattermost-mcp) with 0600
permissions and refreshed transparently thereafter.
Note: Mattermost OAuth2 has no granular scopes — the token inherits the full access of the authorizing user.
Legend: MM_READ_ONLY=true) · 💥 = destructive (requires
MM_ALLOW_DESTRUCTIVE=true and confirm: true per call)
| Tool | Description |
|---|---|
post_message |
Post a message to a channel (optional root_id, file_ids, props). |
reply_thread |
Reply within a thread (root_id). |
get_channel_posts |
Paginated channel history (page/per_page, or since/before/after cursors). |
get_thread |
A root post and all its replies. |
get_post |
A single post by id. |
search_posts |
Full-text search within a team (terms, is_or_search). |
edit_post |
Update a post's message (patch — message field only). |
delete_post 💥 |
Delete a post. |
send_dm |
Open/get a DM (2 users) or group DM (3–8 users) and post a message. |
pin_post |
Pin a post. |
unpin_post |
Unpin a post. |
add_reaction |
Add an emoji reaction (as the authenticated user). |
remove_reaction |
Remove an emoji reaction. |
| Tool | Description |
|---|---|
list_teams |
Teams the authenticated user belongs to. |
list_channels |
The user's channels in a team. |
get_channel |
Channel metadata. |
create_channel |
Create a public (O) or private (P) channel. |
join_channel |
Join a channel (self). |
leave_channel |
Leave a channel (self). |
archive_channel 💥 |
Archive (soft-delete) a channel. |
list_channel_members |
Members of a channel (paginated). |
add_member |
Add another user to a channel. |
remove_member 💥 |
Remove another user from a channel. |
get_unreads |
Per-channel unread/mention counts for a team. |
mark_channel_read |
Mark a channel as viewed. |
| Tool | Description |
|---|---|
get_me |
The authenticated user. |
get_user |
A user by id or username (exactly one required). |
search_users |
Search users by term. |
get_user_status |
A user's status (online/away/dnd/offline). |
set_status |
Set own status (online | away | offline | dnd). |
set_custom_status |
Set own custom status (emoji + text). |
| Tool | Description |
|---|---|
upload_file |
Upload a file to a channel (base64 content); returns file ids to attach via post_message.file_ids. |
get_file |
Download a file's content as base64 (configurable max_bytes cap, default 8 MiB). |
get_file_metadata |
File info (GET /files/{id}/info). |
| Variable | Effect |
|---|---|
MM_READ_ONLY=true |
Every tools/list. |
MM_ALLOW_DESTRUCTIVE=true |
Required to unlock the 💥 tools. |
MM_TEAM_ALLOWLIST |
CSV of permitted team ids; calls targeting other teams are refused. |
MM_CHANNEL_ALLOWLIST |
CSV of permitted channel ids; calls targeting other channels are refused. |
MM_MAX_MESSAGE_LEN |
Messages exceeding this length (Unicode code points) are rejected before sending. |
- Destructive tools (
delete_post,archive_channel,remove_member) require bothMM_ALLOW_DESTRUCTIVE=truein the environment and an explicitconfirm: trueargument in the tool call. MM_READ_ONLY=truetakes precedence overMM_ALLOW_DESTRUCTIVE; all⚠️ /💥 tools refuse.- Allowlists are checked against the resolved team/channel id for the call, not a display name.
- Mattermost API errors (status code + message) are surfaced verbatim — never swallowed.
The server speaks MCP over stdio. Point your MCP client at the binary. Two ready-to-edit config
examples are in the examples/ directory.
If you installed globally and ran mattermost-mcp login, the credentials are already on disk — the
client config needs no env block at all:
{
"mcpServers": {
"mattermost": {
"command": "mattermost-mcp"
}
}
}Suitable for any MCP client that follows the standard mcpServers schema (Cursor, Continue, etc.).
Copy to .mcp.json in your project root and adjust the path and credentials:
{
"mcpServers": {
"mattermost": {
"command": "node",
"args": ["/absolute/path/to/mattermost-mcp/dist/index.js"],
"env": {
"MM_URL": "https://mattermost.example.com",
"MM_AUTH_MODE": "pat",
"MM_TOKEN": "your-personal-access-token"
}
}
}
}Merge into ~/Library/Application Support/Claude/claude_desktop_config.json on macOS (or the
equivalent path on Windows/Linux). Always use absolute paths. Restart Claude Desktop after
saving.
{
"mcpServers": {
"mattermost": {
"command": "node",
"args": ["/absolute/path/to/mattermost-mcp/dist/index.js"],
"env": {
"MM_URL": "https://mattermost.example.com",
"MM_AUTH_MODE": "pat",
"MM_TOKEN": "your-personal-access-token"
}
}
}
}A docker-compose.yml ships in the repository. It starts a disposable Mattermost preview
instance:
docker compose up -d
# Mattermost is now running at http://localhost:8065
# Create an account + team, then generate a Personal Access Token.Live integration tests are skipped by default (so npm test stays offline and green). Set
MM_INTEGRATION=1 to enable them:
MM_INTEGRATION=1 \
MM_URL=http://localhost:8065 \
MM_AUTH_MODE=pat \
MM_TOKEN=your-token \
MM_TEST_CHANNEL_ID=<channel-id> \
npm testMM_TEST_CHANNEL_ID is optional; it only enables the post → get → delete write round-trip tests.
| Script | Purpose |
|---|---|
npm run build |
Compile TypeScript → dist/ |
npm run dev |
Run the server from source over stdio (tsx, no build needed) |
npm start |
Run the built server (node dist/index.js) |
npm test |
Run the vitest suite (integration tests skipped without MM_INTEGRATION=1) |
npm run lint |
ESLint + Prettier check |
npm run format |
Prettier write (auto-fix formatting) |
MIT