Content
# MCP Server Lenza
MCP Server for working with the messenger [Lenza](https://lenzaos.com): reading (chats, messages, search, activity) and writing (sending, rich text, reactions, threads, quote-reply, mentions). 33 tools.
## Installation
```bash
pip install -r requirements.txt
```
## Config
Priority: environment variables → `config.json` next to `server.py`.
| Parameter | Env | Description |
|---|---|---|
| `token` | `LENZA_TOKEN` | Permanent API token (get: `lenza-get-token.ps1`, item 1) |
| `base_url` | `LENZA_BASE_URL` | Default `https://api.lenzaos.com` |
| `domain` | `LENZA_DOMAIN` | Domain of the space, needed for `lenza_get_user` by short login |
`config.json` (template — `config.example.json`, the file itself is in `.gitignore`):
```json
{
"token": "NKHF_...",
"base_url": "https://api.lenzaos.com",
"domain": "your-domain"
}
```
## Launch
```bash
python server.py
```
### Connecting in Claude Code
```json
{
"mcpServers": {
"lenza": {
"command": "python",
"args": ["/path/to/mcp-lenza/server.py"]
}
}
}
```
## Read Tools (21)
| Tool | Returns |
|---|---|
| `lenza_get_unread` | Unread chats, N last messages |
| `lenza_get_chats` | List of chats (filter: all/private/group/channel/bot) |
| `lenza_get_chat_info` | Chat info + participants |
| `lenza_get_messages` | Last N messages of the chat |
| `lenza_get_messages_page` | Browse history (prev/next/current from anchor) |
| `lenza_search_messages` | Search by text (globally or by chat) |
| `lenza_get_activity` | Activity panel: MENTION/THREAD/REACTION/ALL |
| `lenza_get_last_messages_multi` | Last messages from multiple chats in batch |
| `lenza_count_messages` | Number of messages for a period |
| `lenza_get_chat_links` | Links shared in the chat |
| `lenza_get_user` | Profile by login / login@domain / hash |
| `lenza_get_chat_users` | Chat participants |
| `lenza_get_contacts` | User contacts |
| `lenza_get_common_chats` | Common chats with the user |
| `lenza_get_online_status` | Last online (batch) |
| `lenza_get_who_read` | Who read the message |
| `lenza_get_attachments` | Chat files |
| `lenza_get_pinned_messages` | Pinned messages of the chat |
| `lenza_get_profile` | Your profile (to determine "my" mentions) |
| `lenza_get_notices` | Notifications (reminders) |
| `lenza_get_unread_count` | Unread counter |
## Write Tools (12)
| Tool | Action |
|---|---|
| `lenza_send_message` | Send a message (+ attachments, mentions, HTML/MD-markup) |
| `lenza_send_formatted_message` | Rich Text: 11 styles via `styles` with precise positioning |
| `lenza_add_reaction` / `lenza_remove_reaction` | Reaction (emoji) to a message |
| `lenza_create_thread` | Create a thread by a message |
| `lenza_reply_in_thread` | Reply in the thread (creates a thread if it doesn't exist) |
| `lenza_reply_message` | Reply to a message (quote-reply with a quote) |
| `lenza_forward_message` | Forward to another chat |
| `lenza_mark_read` | Mark the chat as read |
| `lenza_read_mention` | Mark the mention as read (remove the "@" badge) |
| `lenza_edit_message` | Edit your message |
| `lenza_cancel_message` | Cancel (delete) your message |
## Rich Text (`lenza_send_formatted_message`)
Styles are specified by the `styles` list. It's better to pass the position through `target` (substring from the text) — `offset`/`length` are calculated by the server; manual `offset`/`length` are fallback.
```json
{
"chat_hash": "...",
"text": "Sprint results\nTask PROJ-123 closed.\nLink to report\nfirst item\nsecond item",
"styles": [
{"type": "h1", "target": "Sprint results"},
{"type": "code", "target": "PROJ-123"},
{"type": "link", "target": "Link to report", "url": "https://example.com"},
{"type": "ordered_list", "target": "first item\nsecond item"}
]
}
```
Types: `bold`, `italic`, `underline`, `strikethrough`, `code`, `code_block` (+`language`), `quote`, `h1`, `link` (+`url`), `unordered_list`, `ordered_list`.
## Mentions
The server does not resolve logins from the text — mentions work only through `user_hash` (taken from `lenza_get_chat_users` / `lenza_get_user`):
```json
{"chat_hash": "...", "text": "@Name, look",
"mentions": [{"user_hash": "<uuid>", "text": "@Name"}]}
```
`"user_hash": "all"` — mention all in the chat.
## Limitations and Features
- **Threads: client cache.** Threads created through the API work correctly on the backend, but the response counter in the Lenza client may not update immediately — updating the chat (Ctrl+R) or restarting the client helps.
- Messages are sent **on behalf of the token owner** — there is no separate bot identity for the API.
- `/message/{hash}/list` does not accept `limit` — it returns ~30–60 messages; for deep history, use `lenza_get_messages_page`.
- `bot_assistant` chats (Lenzabot) do not return history (HTTP 500) — they are skipped.
## Typical Scenarios
- **“Check unread”** → `lenza_get_unread` or `lenza_get_activity("MENTION")`
- **“Summary by channel X for the day”** → `lenza_get_chats` → `lenza_get_messages(hash, 500)`
- **“Summary by all channels”** → `lenza_get_last_messages_multi([hash1, hash2, ...])`
- **“Reply to Arsen”** → `lenza_get_unread` → `lenza_send_message(chat_hash, ...)`
- **“Reply to this message”** → `lenza_reply_message(chat_hash, message_hash, ...)`
- **“Discuss in the thread”** → `lenza_reply_in_thread(message_hash, ...)`
- **“Put a reaction”** → `lenza_add_reaction(message_hash, "👍")`
## Found API Features
Official API documentation: https://api.lenzaos.com/doc
(local copy of swagger — `docs/lenza_api_swagger.yaml`).
- `/user` and `/chat/get` return `{created:[...]}`, not a flat array.
- Message pagination (`/message/{hash}/prev|next|current`) returns `{item:[...]}`.
- `/user/by-login/` expects `login@domain`, not short login.
- Message text is in the `message` field as a JSON string `{"text","entities","version":2}`;
sending also requires this format (otherwise plain text).
- **Reaction is set by `POST /message/{hash}/reaction`** (in the spec only GET/DELETE).
- **Reply-link** is not created by POST /message fields — a thread is used (`POST /chat type=thread message_hash=...`), the thread is a separate chat.
- Entity types are confirmed by removing the etalon from the GUI client: strikethrough — `MessageEntityStrike` (not Strikethrough), link — `MessageEntityTextUrl` (not MessageEntityLink).
- The “@” mention badge is removed by a separate `PUT /message/{hash}/read/mention`.
## Not Implemented (Not Required)
Creating/deleting groups and channels, managing participants, settings.