Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Matrix Nio

БесплатноНе проверен

A Model Context Protocol server for Matrix that gives AI assistants read/write access to Matrix rooms, semantic search over message history, and real-time messa

GitHubEmbed

Описание

A Model Context Protocol server for Matrix that gives AI assistants read/write access to Matrix rooms, semantic search over message history, and real-time message notifications.

README

A Model Context Protocol server for Matrix, built on matrix-nio. Gives AI assistants read/write access to Matrix rooms, semantic search over message history, and real-time message notifications.

Features

  • get_recent_messages — fetch the most recent messages across all joined rooms, with optional filtering by exact MXID sender or room
  • search_messages — search indexed message history by semantic similarity (OpenAI embeddings + cosine similarity), fuzzy sender name, time range, or any combination
  • get_message_context — retrieve messages surrounding a specific event (useful after a search hit)
  • get_room_info — return the friendly display name and full member list (MXID + display name) for a room
  • send_message — send a text message to any joined room
  • LLM callback — call any OpenAI-compatible endpoint with a configurable prompt after a cooldown period; multiple messages are batched; also stream events via SSE
  • E2EE support — works with encrypted rooms via libolm
  • Backfill on startup — indexes historical messages from all joined rooms before going live

Requirements

  • A Matrix account with a long-lived access token and a stable device ID
  • An OpenAI API key (for text-embedding-3-small embeddings)
  • Docker and Docker Compose (for running the server and Qdrant)

Quick start

git clone <this repo>
cd nio-mcp
cp .env.example .env
# Edit .env — fill in MATRIX_*, OPENAI_API_KEY at minimum
docker compose up --build

The MCP server is available at http://localhost:8000/mcp (Streamable HTTP transport). The Matrix event SSE stream is at http://localhost:8000/events.

Configuration

All configuration is via environment variables. Copy .env.example to .env and fill in the required values.

Variable Required Default Description
MATRIX_HOMESERVER_URL yes Homeserver URL, e.g. https://matrix.example.org
MATRIX_ACCESS_TOKEN yes Long-lived access token
MATRIX_USER_ID yes Full MXID, e.g. @bot:example.org
MATRIX_DEVICE_ID yes Device ID — must be stable across restarts for E2EE
MATRIX_STORE_PATH no ~/.cache/nio-mcp/store Path for the Olm E2EE crypto database (created if absent)
MATRIX_KEY_BACKUP_FILE no Path to an Element-exported E2EE key file; see Decrypting historical messages
MATRIX_KEY_BACKUP_PASSPHRASE no Passphrase chosen when exporting; required when MATRIX_KEY_BACKUP_FILE is set
QDRANT_HOST no localhost Qdrant hostname (qdrant inside Docker Compose)
QDRANT_PORT no 6333 Qdrant port
QDRANT_COLLECTION no matrix_messages Qdrant collection name
OPENAI_API_KEY yes OpenAI API key for embeddings
EMBEDDING_MODEL no text-embedding-3-small OpenAI embedding model; dimensions is only supported by text-embedding-3-* models
EMBEDDING_VECTOR_SIZE no 1536 Output dimension requested from the model and used for the Qdrant collection; see note below
WEBHOOK_URL no OpenAI-compatible base URL for the LLM callback (e.g. https://api.openai.com/v1)
WEBHOOK_BEARER_TOKEN no Bearer token sent in the Authorization header
WEBHOOK_PROMPT_HEADER no New Matrix messages: Text prepended once before all per-message lines
WEBHOOK_PROMPT_PER_MSG no {sender_name} ({sender}) in {room_name} ({room}): {message} Template rendered once per buffered message
WEBHOOK_MODEL no gpt-4o-mini Model name passed to the LLM
WEBHOOK_COOLDOWN_SECONDS no 300 Seconds of silence before the LLM is called; multiple messages within the window are batched
WEBHOOK_TOOLS no Optional JSON string of tools/parameters merged into the chat completions request body
BACKFILL_LIMIT no 100 Messages fetched per page per room during startup backfill
BACKFILL_PAGES_MAX no 10 Maximum backfill pages per room; 0 = full history
MESSAGE_BUFFER_SIZE no 500 In-memory ring buffer size for get_recent_messages
MATRIX_SYNC_TIMEOUT_MS no 30000 Matrix /sync long-poll timeout in milliseconds
SSE_QUEUE_MAXSIZE no 100 Per-subscriber SSE event queue cap (oldest dropped when full)
MCP_PORT no 8000 Port for the HTTP server; MCP at /mcp, Matrix event SSE at /events, health at /health
HTTP_AUTH_TOKEN no If set, requires Authorization: Bearer <token> for all endpoints
ALLOW_SEND_MESSAGE no false Set to true to enable the send_message tool
IGNORED_ROOMS no Comma-separated list of Matrix room IDs to exclude from indexing, backfill, and live sync (e.g. !abc:example.org,!def:example.org); leave empty to include all rooms

Changing EMBEDDING_MODEL or EMBEDDING_VECTOR_SIZE requires wiping the Qdrant collection and re-syncing from scratch. The collection is created at startup with the configured vector size; vectors already stored at a different dimension will cause Qdrant errors that cannot be recovered without dropping the collection. To reset: stop the server, delete the Qdrant collection (or point QDRANT_COLLECTION at a new name), delete MATRIX_STORE_PATH/backfill_complete, then restart.

Obtaining credentials

Access token and device ID — the easiest way is via Element:

  1. Log in to Element as your bot account
  2. Go to Settings → Security & Privacy → Session Manager
  3. Copy the access token and session/device ID for your current session

Alternatively, call the Matrix login endpoint directly:

curl -XPOST 'https://matrix.example.org/_matrix/client/v3/login' \
  -H 'Content-Type: application/json' \
  -d '{"type":"m.login.password","user":"@bot:example.org","password":"secret"}'

The response contains access_token and device_id.

MCP tools

get_recent_messages

Returns the k most recent messages from the in-memory buffer (populated by backfill and live sync). During the initial startup backfill, this endpoint returns an empty array until the backfill phase has finished populating the buffer.

{
  "k": 20,
  "sender": "@alice:example.org",
  "room_id": "!abc123:example.org"
}

sender and room_id are optional filters. sender must be an exact MXID (e.g. @alice:example.org) — partial names are not matched. Returns a list of message objects:

[
  {
    "event_id": "$abc:example.org",
    "room_id": "!abc123:example.org",
    "sender": "@alice:example.org",
    "body": "Hello!",
    "timestamp": 1700000000000
  }
]

search_messages

Search indexed messages by semantic similarity, sender, time range, or any combination of those. At least one of query, sender, after_ts, or before_ts must be provided.

{
  "query": "project standup notes",
  "sender": "fred",
  "limit": 10,
  "after_ts": 1700000000000,
  "before_ts": 1700086400000
}
  • query — natural-language search; embedded with OpenAI and matched by cosine similarity against Qdrant.
  • sender — fuzzy sender filter. A full MXID (@alice:example.org) is matched exactly; anything else (e.g. alice, fred) uses word search against the sender's MXID, display name, and localpart variants.
  • after_ts / before_ts — Unix millisecond timestamps (optional). Filter results to a time window.
  • If query is omitted, up to limit matching messages are returned newest-first by timestamp with score: 0.

Returns the same message fields as get_recent_messages plus a score (cosine similarity, 0–1, or 0 for time-only queries). Use the returned event_id and room_id with get_message_context to retrieve surrounding messages.

get_message_context

Fetches messages before and after a specific event via the Matrix /context endpoint.

{
  "room_id": "!abc123:example.org",
  "event_id": "$found_event:example.org",
  "before": 5,
  "after": 5
}

get_room_info

Returns the friendly display name and full member list for a room, read from nio's in-memory room state populated during initial sync.

{
  "room_id": "!abc123:example.org"
}

Returns:

{
  "room_id": "!abc123:example.org",
  "name": "My Room",
  "members": [
    {"user_id": "@alice:example.org", "display_name": "Alice"},
    {"user_id": "@bob:example.org", "display_name": "Bob"}
  ]
}

send_message

Sends a plain-text message to a room.

{
  "room_id": "!abc123:example.org",
  "body": "Hello from the MCP server!"
}

Webhooks

LLM callback

When WEBHOOK_URL is set, an OpenAI-compatible chat-completions request is sent after a configurable cooldown period with no new messages (default 5 minutes). Multiple messages arriving within the cooldown window are batched into a single call.

POST {WEBHOOK_URL}/chat/completions
Authorization: Bearer {WEBHOOK_BEARER_TOKEN}
Content-Type: application/json

{
  "model": "gpt-4o-mini",
  "messages": [{ "role": "user", "content": "<rendered prompt>" }]
}

WEBHOOK_PROMPT_HEADER is prepended once. WEBHOOK_PROMPT_PER_MSG is rendered for every buffered message and the results are joined with newlines. Braces inside message bodies are never re-interpreted as placeholders.

Placeholder Value
{message} Body of the message
{sender_name} Display name of the sender
{sender} MXID of the sender
{room_name} Display name of the room
{room} Room MXID

Example:

WEBHOOK_PROMPT_HEADER=Summarize these Matrix messages and list action items:
WEBHOOK_PROMPT_PER_MSG={sender_name} said: {message}

Produces a user message like:

Summarize these Matrix messages and list action items:
Alice said: Can we move the standup?
Bob said: Sure, how about 10am?

SSE stream

Connect to http://localhost:8000/events to receive a live stream of new messages:

curl -N http://localhost:8000/events

Each event is a JSON-encoded message object. Multiple clients can connect simultaneously — each gets its own independent stream. If a client falls behind by more than SSE_QUEUE_MAXSIZE events, the oldest queued events are dropped (the stream remains live but is lossy under load).

Health check

curl http://localhost:8000/health
# {"status":"ok"}

Architecture

┌──────────────────────────────────────────────────────────┐
│                      nio-mcp process                     │
│                                                          │
│  ┌────────────────────────────────────────────────────┐  │
│  │         FastAPI  :8000                             │  │
│  │   /mcp  (MCP Streamable HTTP)                      │  │
│  │   /events  (Matrix message SSE fan-out)            │  │
│  │   /health                                          │  │
│  └───────────────────────┬────────────────────────────┘  │
│                          │                               │
│             ┌────────────▼────────────┐                  │
│             │     MatrixMCPClient     │                  │
│             │    (nio AsyncClient)    │                  │
│             └──────┬─────────────────┘                  │
│                    │                                     │
│       ┌────────────┼──────────────┐                     │
│       │            │              │                     │
│  ┌────▼───┐  ┌─────▼────┐  ┌─────▼───────────┐        │
│  │Qdrant  │  │ OpenAI   │  │WebhookDispatcher│        │
│  │vector  │  │embeddings│  │ LLM call + SSE  │        │
│  │store   │  │          │  │  per-subscriber │        │
│  └────────┘  └──────────┘  └────────────────┘        │
└──────────────────────────────────────────────────────────┘

Startup sequence:

  1. os.makedirs ensures the Olm store directory exists
  2. restore_login() loads credentials from env vars (works on first run — no prior session needed)
  3. Initial sync(full_state=True) anchors the sync token
  4. Backfill: for each joined room, paginate backwards from the initial sync's prev_batch token until end is absent (Matrix spec) or BACKFILL_PAGES_MAX is reached
  5. Register live message callback, then sync_forever(since=<initial token>) — no gap between backfill and live

E2EE note: On a brand-new deployment the Olm store is empty, so messages encrypted before this device joined cannot be decrypted. New messages in encrypted rooms will be decryptable once device trust is established. Plaintext rooms are unaffected. To recover historical encrypted messages see Decrypting historical messages.

Decrypting historical messages in encrypted rooms

Matrix Megolm session keys are distributed once at send-time to all devices present in the room. Because the bot's device wasn't present when those sessions were created, it cannot decrypt historical ciphertext — the homeserver only stores encrypted blobs.

Element (and other standard clients) let you export all session keys you hold to an encrypted file. Importing that file into nio-mcp gives the bot the keys it needs to decrypt backfilled history.

One-time setup

  1. In Element, go to Settings → Security & Privacy → Export E2E room keys.

  2. Choose a passphrase and save the exported .txt file.

  3. Mount the file into the container and set the two config variables:

    services:
      nio-mcp:
        volumes:
          - ./element_keys.txt:/data/element_keys.txt:ro
        environment:
          MATRIX_KEY_BACKUP_FILE: /data/element_keys.txt
          MATRIX_KEY_BACKUP_PASSPHRASE: your-export-passphrase
    
  4. Start (or restart) the bot. On the first run the keys are imported into the Olm store and a sentinel file (key_backup_imported) is written to MATRIX_STORE_PATH. Subsequent restarts skip the import automatically — the env vars and volume mount can be left in place or removed; either way the import will not run again.

Notes

  • Both MATRIX_KEY_BACKUP_FILE and MATRIX_KEY_BACKUP_PASSPHRASE must be set together.
  • The import happens before backfill, so session keys are available when historical messages are paginated.
  • If the file path or passphrase is wrong, startup fails with a clear error rather than silently skipping messages.
  • To force a re-import (e.g. after exporting a newer key file), delete MATRIX_STORE_PATH/key_backup_imported and restart.

Development

Running tests

Unit tests have no external dependencies:

python -m venv .venv
source .venv/bin/activate
pip install matrix-nio mcp qdrant-client openai fastapi "uvicorn[standard]" \
    pydantic-settings httpx anyio sse-starlette \
    pytest pytest-asyncio pytest-mock respx

pytest tests/unit/ -v

Integration tests must be run through the helper script, because it brings up the Docker-backed Matrix homeserver and Qdrant before invoking pytest:

scripts/test-matrix-integration.sh

# Still use the script entry point even when focusing on one integration file
scripts/test-matrix-integration.sh tests/integration/test_qdrant_integration.py

Do not invoke the integration tests with bare pytest unless you have already recreated that environment yourself. The script is the supported entry point; it starts docker-compose.integration.yml, waits for Synapse and Qdrant, runs pytest tests/integration -v "$@", and tears the stack down again.

Project layout

src/nio_mcp/
├── config.py         # Pydantic Settings
├── models.py         # MessageRecord, SearchResult
├── embeddings.py     # OpenAI embedding client
├── vector_store.py   # Qdrant wrapper
├── matrix_client.py  # nio AsyncClient wrapper
├── webhook.py        # HTTP POST + SSE dispatcher
└── server.py         # MCP server + FastAPI app

tests/
├── unit/             # All external I/O mocked
└── integration/      # Real Qdrant + optional real Matrix homeserver coverage

Connecting to Claude Desktop

Start the server with docker compose up --build, then point Claude Desktop at the HTTP endpoint by adding to claude_desktop_config.json:

{
  "mcpServers": {
    "matrix": {
      "url": "http://localhost:8000/mcp"
    }
  }
}

License

MIT

from github.com/james-choncholas/matrix-nio-mcp

Установка Matrix Nio

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/james-choncholas/matrix-nio-mcp

FAQ

Matrix Nio MCP бесплатный?

Да, Matrix Nio MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Matrix Nio?

Нет, Matrix Nio работает без API-ключей и переменных окружения.

Matrix Nio — hosted или self-hosted?

Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.

Как установить Matrix Nio в Claude Desktop, Claude Code или Cursor?

Открой Matrix Nio на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Matrix Nio with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории communication