A Telegram bot and an MCP server for Readeck, in one Go module.
- Telegram bot — multi-tenant. Forward a URL to the bot, it saves to Readeck under the right user. Append
#tagsto set labels. - MCP server — credential-less. Exposes Readeck as tools (
readeck_save,readeck_search,readeck_list_recent) to any MCP client (Claude Desktop, Claude Code, Cursor, etc.). Each client passes its own Readeck token over the wire — the server stores no secrets.
Both share one Go module and one Docker image. The included docker-compose.yml defines both services — start whichever you need:
docker compose up -d # both
docker compose up -d mcp # only MCP server
docker compose up -d bot # only Telegram bot
docker compose stop bot # turn one off laterEach service ignores config sections it doesn't use, so you can have an MCP-only deploy without filling in [telegram] / [[tenants]], and vice versa.
Readeck's REST API authenticates each call with a per-user API token. The bot keeps a map of telegram_id → readeck_token in config.toml and uses the right token per incoming message. Unknown Telegram senders are silently ignored. This lets one bot instance serve a household, a team, or a friend group without anyone seeing anyone else's data.
The MCP server doesn't multi-tenant because an MCP client is, by definition, one user. Run one MCP instance per user if you need to.
- Create a bot with @BotFather, grab the token.
cp config.example.toml config.toml, paste the bot token, setbase_url.- Add yourself to
[[tenants]]:- in Readeck → Settings → API tokens → create a token, paste as
readeck_token - run the bot once and DM it
/whoamito get yourtelegram_id
- in Readeck → Settings → API tokens → create a token, paste as
docker compose up -d bot
Repeat for each new user:
- The user logs into Readeck and creates their own API token (Settings → API tokens).
- They start the bot and send
/whoami— the bot replies with their numeric Telegram id (works for non-tenants too — that's the onboarding hook). - Admin appends a new
[[tenants]]block toconfig.tomlwith the two values, thendocker compose restart bot.
Unknown senders are silently ignored, so the bot is safe to leave running while users onboard themselves.
The MCP server only needs to know the base URL of your Readeck instance. Tokens are not stored on the server — they're passed by each MCP client when connecting.
If you're already running the bot, MCP reads [readeck].base_url from the same config.toml. Otherwise set READECK_BASE_URL env.
docker compose up -d mcpThen in your MCP client config (Claude Code / Claude Desktop / Cursor / etc.):
{
"mcpServers": {
"readeck": {
"type": "http",
"url": "https://your-mcp-host/mcp",
"headers": {
"X-API-Key": "rk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}The token is generated in Readeck → Settings → API tokens. Each user of the MCP server uses their own token; the server hands the call straight to Readeck which scopes it to that user.
Authorization: Bearer <token> is also accepted as a fallback for MCP clients that only know how to send that header.
- Bot:
config.toml. Env overrides:TELEGRAM_TOKEN,READECK_BASE_URL. - MCP: just needs
READECK_BASE_URL(env or[readeck].base_urlin TOML). Optional:MCP_HTTP_ADDRto change the bind address from:8080.
go build ./cmd/bot ./cmd/mcpMIT.