@northbound-run/matrix-agent-mcp
v0.6.1
Published
MCP server for Matrix messaging. Exposes Matrix rooms, messages, members, and media as MCP tools and resources so AI agents can read and write to Matrix over the [Model Context Protocol](https://modelcontextprotocol.io/).
Downloads
2,087
Readme
@northbound-run/matrix-agent-mcp
MCP server for Matrix messaging. Exposes Matrix rooms, messages, members, and media as MCP tools and resources so AI agents can read and write to Matrix over the Model Context Protocol.
Overview
@northbound-run/matrix-agent-mcp connects an MCP client (Claude Desktop, Cursor, or any MCP-compatible host) to one or more Matrix accounts. The server maintains a persistent sync connection to each account and exposes 31 tools covering messaging, room management, membership, room state, media, and account operations.
Events stream in real time via MCP resource subscriptions. Agents can subscribe to agentchannels://events to receive all Matrix events, or filter to a specific room with agentchannels://events/{roomId}. The current room list is available at agentchannels://rooms.
Quick Start
Using environment variables (single account):
MATRIX_ACCESS_TOKEN=<token> \
MATRIX_USER_ID=@bot:matrix.org \
MATRIX_DEVICE_ID=MYDEVICE \
MATRIX_HOMESERVER_URL=https://matrix.org \
bunx @northbound-run/matrix-agent-mcpUsing a credentials file:
bunx @northbound-run/matrix-agent-mcp --credentials ./creds.jsonThe server starts on http://0.0.0.0:3100/mcp by default.
Configuration Reference
CLI Flags
| Flag | Description | Default |
|---|---|---|
| --credentials <path> | Path to a JSON credentials file (single object or array) | — |
| --homeserver <url> | Matrix homeserver URL | https://matrix.agentchannels.dev |
| --port <n> | HTTP port to listen on | 3100 |
| --host <addr> | HTTP host to bind to | 0.0.0.0 |
| --store-path <path> | Path to the credential store directory | ~/.matrix-agent |
| --hosted | Enable hosted mode (Bearer token auth required) | false |
| --bearer-token <token> | Bearer token for hosted mode auth | — |
Environment Variables
| Variable | Description |
|---|---|
| MATRIX_ACCESS_TOKEN | Access token for a Matrix account |
| MATRIX_USER_ID | Full Matrix user ID (e.g. @bot:matrix.org) |
| MATRIX_DEVICE_ID | Device ID for the session |
| MATRIX_HOMESERVER_URL | Homeserver URL (optional, defaults to https://matrix.agentchannels.dev) |
| PORT | HTTP port (overridden by --port) |
| HOSTED_MODE | Set to true to enable hosted mode |
| MCP_BEARER_TOKEN | Bearer token for hosted mode (overridden by --bearer-token) |
Tool Reference
Messaging (7 tools)
| Tool | Description |
|---|---|
| matrix_send | Send a message to a room. Supports text, HTML, notice, emote, image, audio, video, and file types. |
| matrix_edit_message | Edit a previously sent message. |
| matrix_redact_message | Redact (delete) a message from a room. |
| matrix_react | Add an emoji reaction to a message. |
| matrix_mark_read | Mark a message as read, advancing the read receipt. |
| matrix_typing | Send or stop a typing indicator. |
| matrix_get_messages | Retrieve recent messages from a room with optional pagination. |
Rooms (8 tools)
| Tool | Description |
|---|---|
| matrix_list_rooms | List all rooms the account is a member of. |
| matrix_create_room | Create a new room with optional name, topic, preset, and initial invites. |
| matrix_join_room | Join a room by ID or alias. |
| matrix_leave_room | Leave a room. |
| matrix_invite | Invite a user to a room. |
| matrix_get_room_info | Get name, topic, member count, and encryption status for a room. |
| matrix_set_room_topic | Update the room topic. |
| matrix_set_room_name | Update the room display name. |
Members (5 tools)
| Tool | Description |
|---|---|
| matrix_list_members | List all members of a room with their membership state. |
| matrix_kick | Kick a user from a room (requires moderator power level). |
| matrix_ban | Ban a user from a room (requires moderator power level). |
| matrix_unban | Unban a previously banned user. |
| matrix_set_power_level | Set a user's power level in a room (requires admin). |
State (3 tools)
| Tool | Description |
|---|---|
| matrix_get_state | Read a specific state event by type and optional state key. |
| matrix_set_state | Write a state event (requires appropriate power level). |
| matrix_get_all_state | Get all current state events for a room. |
Media (3 tools)
| Tool | Description |
|---|---|
| matrix_upload_media | Upload a base64-encoded file to the homeserver. Returns an MXC URL. |
| matrix_download_media | Download media by MXC URL, returned as base64. |
| matrix_get_media_url | Convert an MXC URL to an HTTPS URL without downloading the file. |
Account (4–5 tools)
| Tool | Description |
|---|---|
| matrix_get_profile | Get profile info (display name, avatar) for any user. |
| matrix_set_display_name | Set the display name for the current account. |
| matrix_set_avatar | Set the avatar using an MXC URL. |
| matrix_set_presence | Set presence state: online, offline, or unavailable. |
| matrix_login | Log in with username and password. Returns credentials. Disabled in hosted mode. |
Multi-Account Setup
Pass an array in your credentials file to connect multiple accounts simultaneously. Each tool accepts an optional account parameter (a Matrix user ID) to select which account to use. If omitted, the first account in the list is used.
credentials.json (array):
[
{
"accessToken": "token_for_bot1",
"userId": "@bot1:matrix.org",
"deviceId": "BOT1DEV",
"homeserverUrl": "https://matrix.org"
},
{
"accessToken": "token_for_bot2",
"userId": "@bot2:matrix.org",
"deviceId": "BOT2DEV",
"homeserverUrl": "https://matrix.org"
}
]Tool call with explicit account:
{
"tool": "matrix_send",
"arguments": {
"roomId": "!abc:matrix.org",
"message": "Hello from bot2",
"account": "@bot2:matrix.org"
}
}Resource Subscriptions
The server exposes three MCP resources that stream live Matrix data:
| URI | Description |
|---|---|
| agentchannels://events | All events across all accounts (last 50). |
| agentchannels://events/{roomId} | Events filtered to a specific room (last 50). |
| agentchannels://rooms | Current list of joined rooms for all accounts. |
Each resource sends an MCP ResourceUpdated notification when new data arrives, so subscribed clients receive updates without polling.
Event types included: message, message.edit, message.redact, reaction, reaction.redact, room.join, room.leave, room.invite, room.update, room.encrypted, member.join, member.leave, member.invite, typing, receipt, connection, sync.
Error Handling
All tool errors return a structured MCP error response with a human-readable message. Common errors:
| Error | Message pattern |
|---|---|
| Token expired | Access token expired. Re-authenticate. |
| Rate limited | Rate limited. Retry after Nms. |
| Room not found | Room not found: !roomId:server |
| Not in room | Not a member of room: !roomId:server |
| Insufficient power level | Insufficient permissions in room: !roomId:server |
| Network error | Network error: <details> |
| Homeserver error | Homeserver error (500): <details> |
Non-Matrix errors (e.g. invalid tool arguments) are re-thrown and handled by the MCP SDK.
Hosted Mode
Hosted mode enables Bearer token authentication on the /mcp endpoint. This is intended for deployments where the MCP server is exposed over a network and must be protected.
HOSTED_MODE=true \
MCP_BEARER_TOKEN=your-secret-token \
bunx @northbound-run/matrix-agent-mcp --credentials ./creds.jsonOr via flags:
bunx @northbound-run/matrix-agent-mcp \
--hosted \
--bearer-token your-secret-token \
--credentials ./creds.jsonIn hosted mode:
- All
/mcprequests must includeAuthorization: Bearer <token> - The
/healthendpoint is always unauthenticated matrix_loginis disabled (credentials are managed externally)
Health Check
curl http://localhost:3100/healthReturns JSON with status ok, degraded, or error, plus per-account sync state. HTTP 503 is returned when status is error.
Multi-tenant Hosted Mode
Multi-tenant hosted mode runs the same binary as a shared endpoint where each caller authenticates with their own Matrix access token per request. No shared bot account is needed. This is the mode used by mcp.agentchannels.dev.
Enable it by setting MCP_MULTI_TENANT=true. In this mode the server skips all boot-time credential loading and instead validates each incoming request against the Matrix homeserver via /_matrix/client/v3/account/whoami.
Required Headers
Every request to /mcp must include:
| Header | Required | Description |
|---|---|---|
| Authorization: Bearer <matrix_access_token> | Yes | Your Matrix access token from the homeserver named in MATRIX_HOMESERVER_URL. Validated via /whoami on each request (with a short positive cache). |
| X-Matrix-Homeserver: <url> | No | If sent, must equal the server's configured MATRIX_HOMESERVER_URL (case-insensitive, trailing-slash tolerant). If omitted, the configured URL is used. Send it only if your client allows custom headers and you want explicit homeserver pinning. |
| Mcp-Session-Id: <uuid> | No | Managed by the MCP Streamable HTTP transport. Round-trip whatever the server returns in this header — omitting it causes each request to be treated as a new session. |
Error Codes
Authentication and rate-limiting errors return a JSON body:
{ "errcode": "M_MISSING_TOKEN", "error": "Missing or malformed Authorization header" }| Code | HTTP Status | Meaning |
|---|---|---|
| M_MISSING_TOKEN | 401 | Authorization header absent or malformed |
| M_UNKNOWN_TOKEN | 401 | Token rejected by homeserver whoami |
| AC_HOMESERVER_NOT_ALLOWED | 403 | X-Matrix-Homeserver does not match the server's configured MATRIX_HOMESERVER_URL |
| M_UPSTREAM_ERROR | 502 | Homeserver unreachable or returned an unexpected error during whoami |
| AC_RATE_LIMITED | 429 | Per-token rate limit exceeded; Retry-After header indicates seconds to wait |
Warm-sync Semantics
Default: stateless. Each tool call issues direct HTTP requests to the homeserver. No /sync loop runs. This keeps resource usage low and latency predictable for the common case of tool invocations.
Warm sessions (opt-in). Some functionality — specifically the agentchannels://events* resources — requires a running /sync loop to receive real-time events. When a client subscribes to those resources, the server promotes the session to "warm" and starts /sync for that token.
V1 limitation. The MCP SDK does not expose the resources/subscribe lifecycle event to handler code (this is the gap described in US-010). As a result, warm promotion on subscribe is not automatic in V1. The agentchannels://events* resource URI is registered and accessible, but live event streaming may not function in V1 — it is available for future iteration once the SDK exposes the necessary hook.
Sync-token persistence. When REDIS_URL is set, warm-session sync tokens are persisted under the key mcp:synctoken:<token_hash> with a 24-hour TTL. This means warm sessions resume from where they left off after a container restart, avoiding a full timeline re-bootstrap. If REDIS_URL is unset or Redis is unreachable, the server falls back to in-memory storage automatically (single-instance only; tokens are lost on restart).
Idle eviction. Warm sessions that receive no tool calls for longer than MCP_IDLE_SESSION_TTL_MS (default 15 minutes) are reaped automatically. The /sync loop is stopped and the token persisted to Redis before eviction.
E2EE Not Supported
Hosted multi-tenant mode does not support end-to-end encrypted rooms. Tools that target an encrypted room return a clear error message rather than silently returning ciphertext. Users who require E2EE should run the local single-tenant package (bunx @northbound-run/matrix-agent-mcp) with their own credentials, where the full encryption stack is available.
Configuration
| Variable | Default | Description |
|---|---|---|
| MCP_MULTI_TENANT | — | Set to true to enable multi-tenant hosted mode |
| MATRIX_HOMESERVER_URL | https://matrix.agentchannels.dev | The only homeserver accepted for token validation and tool calls |
| REDIS_URL | — | Redis connection URL for sync-token persistence. Falls back to in-memory if unset. |
| MCP_RATE_LIMIT_RPS | 5 | Per-token sustained request rate (requests per second) |
| MCP_RATE_LIMIT_BURST | 30 | Per-token burst capacity (initial token-bucket size) |
| MCP_WHOAMI_CACHE_TTL_MS | 300000 | Whoami positive-cache TTL in milliseconds (5 minutes) |
| MCP_IDLE_SESSION_TTL_MS | 900000 | Idle warm-session eviction TTL in milliseconds (15 minutes) |
| MCP_MAX_WARM_SESSIONS | 200 | Maximum concurrent warm /sync sessions (LRU cap) |
| PORT | 3100 / 3300 | HTTP bind port (3100 for local installs, 3300 inside the docker-compose container) |
Mode-Precedence Rule
If both HOSTED_MODE=true and MCP_MULTI_TENANT=true are set simultaneously, multi-tenant wins. The static MCP_BEARER_TOKEN is ignored, matrix_login remains disabled, and /mcp requires per-request Matrix tokens. The server logs the precedence applied at boot:
mode-precedence: multi-tenant overrides static bearer gateIn multi-tenant mode, the account parameter is removed from all tool input schemas. The active Matrix account is determined entirely by the request's bearer token — passing an account argument returns M_INVALID_PARAM.
MCP Client Configuration
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"matrix": {
"command": "bunx",
"args": ["@northbound-run/matrix-agent-mcp"],
"env": {
"MATRIX_ACCESS_TOKEN": "your-access-token",
"MATRIX_USER_ID": "@yourbot:matrix.org",
"MATRIX_DEVICE_ID": "CLAUDE_BOT",
"MATRIX_HOMESERVER_URL": "https://matrix.org"
}
}
}
}Cursor
Add to your Cursor MCP settings:
{
"mcpServers": {
"matrix": {
"command": "bunx",
"args": [
"@northbound-run/matrix-agent-mcp",
"--credentials",
"/path/to/creds.json"
]
}
}
}Remote / Hosted Deployment
For a server already running with hosted mode enabled:
{
"mcpServers": {
"matrix": {
"url": "https://your-server.example.com/mcp",
"headers": {
"Authorization": "Bearer your-secret-token"
}
}
}
}License
MIT
