← Discover MCPs and Agents
k
MCPAI & MLGitHub

kimi-to-im

Telegram bridge for the Kimi CLI — chat with Moonshot Kimi from your phone, sessions persist, single-user, ~1.7K LOC Python, systemd-supervised, MIT.

Links

README

From the repo.

📱 kimi-to-im

Chat with Kimi CLI from Telegram

Self-hosted · single-user · ~1.7K LOC Python · systemd-supervised

License: MIT Python 3.11+ systemd Telegram Kimi

CI GitHub stars GitHub forks


💡 Why this exists

You already use Kimi CLI at your desk. This bridge lets you keep the same conversation going from your phone — over Telegram — without changing how Kimi runs locally. You send a message, the bridge spawns kimi on your machine, and the reply lands back in Telegram. Sessions persist per chat, so follow-ups pick up where you left off.

Single-user, single-host, text-only by design. No cloud component. No account system. The bot replies only to user IDs you explicitly whitelist.


🏗️ Architecture

flowchart LR
    User([📱 You<br/>on Telegram])
    TG[Telegram<br/>Bot API]
    Bridge[bridge daemon<br/>~1.7K LOC Python<br/>systemd --user]
    Kimi[kimi CLI<br/>subprocess<br/>per turn]
    State[(state.json<br/>chat → session)]
    OAuth[~/.kimi/<br/>credentials]

    User -->|message| TG
    TG -->|long-poll<br/>getUpdates| Bridge
    Bridge -->|spawn<br/>--print -S sid| Kimi
    Kimi -->|reasoning_content<br/>+ output| Bridge
    Bridge -->|sendMessage| TG
    TG -->|reply| User
    Bridge <-->|read/write| State
    Kimi <-->|JWT auto-refresh| OAuth

    style User fill:#26A5E4,color:#fff,stroke:#1a8cc4
    style TG fill:#26A5E4,color:#fff,stroke:#1a8cc4
    style Bridge fill:#FF6B35,color:#fff,stroke:#cc5028
    style Kimi fill:#9B59B6,color:#fff,stroke:#7d3f95
    style State fill:#34495E,color:#fff,stroke:#222
    style OAuth fill:#34495E,color:#fff,stroke:#222

🔁 Turn-by-turn flow

sequenceDiagram
    autonumber
    participant U as 📱 User<br/>(Telegram)
    participant T as Telegram<br/>API
    participant D as bridge<br/>daemon
    participant K as kimi<br/>CLI
    participant S as state.json

    U->>T: "Continue the analysis…"
    T-->>D: getUpdates → message
    D->>D: is_authorized(user_id) ?
    D->>S: lookup session_id by chat_id
    S-->>D: sid = abc123…
    D->>T: sendChatAction("typing")
    par heartbeat (every 4s)
        D->>T: typing
    and progress notice (250s, 600s)
        D-->>U: "🤔 Still thinking…"
    and kimi subprocess
        D->>K: kimi --print -S abc123
        K-->>K: K2.6 reasoning + tool calls
        K-->>D: reply text
    end
    D->>T: sendMessage(reply)
    T-->>U: 💬 Kimi's answer
    D->>S: persist (no change, sid sticks)

⚡ Quickstart (5 minutes)

flowchart TD
    A[1\. Get bot token<br/>from @BotFather] --> B[2\. Get your<br/>Telegram user ID<br/>from @userinfobot]
    B --> C[3\. git clone +<br/>install.sh]
    C --> D[4\. Edit config.json<br/>token + allowlist]
    D --> E[5\. systemctl --user<br/>start the unit]
    E --> F[✅ Send hello<br/>to your bot]

    style A fill:#26A5E4,color:#fff
    style B fill:#26A5E4,color:#fff
    style C fill:#9B59B6,color:#fff
    style D fill:#9B59B6,color:#fff
    style E fill:#27AE60,color:#fff
    style F fill:#27AE60,color:#fff

You need: Linux with systemd --user, Python 3.11+, uv, and a working kimi CLI on your PATH. Full prerequisites and verification commands live in docs/deployment.md.

1. Get a Telegram bot token. Message @BotFather, send /newbot, follow the prompts. Copy the token.

2. Get your Telegram user ID. Message @userinfobot. It replies with your numeric ID.

3. Install.

git clone https://github.com/hah23255/kimi-to-im.git ~/.kimi/plugins/telegram-bridge
cd ~/.kimi/plugins/telegram-bridge
bash install.sh

Expected: a .venv/ is created and a systemd user unit registered.

4. Configure.

cp config.example.json config.json
chmod 600 config.json
$EDITOR config.json   # paste bot_token, add your Telegram user ID to allowed_user_ids

5. Start.

systemctl --user start kimi-telegram-bridge.service

Expected: systemctl --user is-active kimi-telegram-bridge.service prints active. Send "hello" to your bot from Telegram — within ~10s the typing indicator appears, then a Kimi reply.

For a more detailed walk-through with pre-flight checks and smoke tests, see docs/deployment.md.


⏱️ Timing & timeouts

gantt
    title Bridge timing budgets per Telegram turn
    dateFormat ss
    axisFormat %Ss

    section Typing indicator
    Refresh every 4s         :active, 0, 4s
    Refresh                  :active, 4, 4s
    Refresh                  :active, 8, 4s

    section Progress notices
    "Still thinking…" @ 250s :crit, 250, 5s
    "Still thinking…" @ 600s :crit, 600, 5s

    section Kimi subprocess
    Allowed work window      :active, 0, 900s
    Hard kill                :crit, 900, 5s

    section JWT
    OAuth token (15min TTL)  :active, 0, 900s
    Auto-refresh             :crit, 600, 1s
EventTime
Typing indicator refreshevery 4 s
First progress notice250 s (~4 min)
Second progress notice600 s (~10 min)
Kimi subprocess hard timeout900 s (15 min, aligned with JWT TTL)
OAuth JWT auto-refresh cadenceevery 10 min (TTL is 15 min)

The 15-minute ceiling is intentional — Kimi K2.6 with thinking on a 160 K-token context routinely needs 5–12 minutes per complex turn. We bound long enough for real work, short enough that truly hung subprocesses get cleaned up.


🛡️ Defence-in-depth

graph TD
    Inbound[Inbound<br/>Telegram message]
    Allowlist{user_id in<br/>allowed_user_ids?}
    ChatlistCheck{chat_id in<br/>allowed_chat_ids?}
    SessionCheck{session_id matches<br/>uuid4 hex?}
    Kimi[Spawn kimi]
    Drop[Dropped silently<br/>+ logged]

    Inbound --> Allowlist
    Allowlist -->|no| Drop
    Allowlist -->|yes| ChatlistCheck
    ChatlistCheck -->|no| Drop
    ChatlistCheck -->|yes| SessionCheck
    SessionCheck -->|no| Drop
    SessionCheck -->|yes| Kimi

    style Drop fill:#E74C3C,color:#fff
    style Kimi fill:#27AE60,color:#fff

The systemd unit ships hardened by default:

LayerMechanism
IdentityDefault-deny allowlist on allowed_user_ids + allowed_chat_ids
Subprocess argvsession_id validated against uuid4-hex regex before being passed to kimi
NetworkRestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
FilesystemPrivateTmp, UMask=0077, config 0600
Kernel surfaceProtectKernelTunables, ProtectKernelModules, LockPersonality
SyscallsSystemCallFilter=@system-service ~@privileged ~@resources
Logshttpx INFO suppressed so bot token never lands in bridge.log
LivenessRestart=on-failure, exit-124 timeout safety net

Full security policy: SECURITY.md. Audit findings: docs/security-scan.md.


📊 Repo at a glance

LanguagePython 3.11+
Source LOC1,698
Test files10 (full suite < 2 s)
External runtime deps1 (httpx)
External system depskimi CLI on PATH, systemd-user
Lines per turn (avg request path)~50
First-launch RAM~22 MB (idle)
Steady-state RAM~80–200 MB depending on Telegram polling state
pie title Source code distribution
    "src/ daemon code" : 720
    "tests/" : 880
    "config + plugin glue" : 98

🧰 What it does and doesn't

This is intentionally a small, opinionated tool.

It does:

  • ✅ Bridge Telegram ↔ Kimi CLI as separate subprocesses per turn
  • ✅ Persist session continuity per chat
  • ✅ Run as a systemctl --user service with hardening
  • ✅ Refresh the OAuth JWT automatically (10-min cadence, 15-min TTL)
  • ✅ Stream typing indicator + progress notices for long turns
  • ✅ Surface friendly error messages (no raw stderr leaks)
  • ✅ Validate inputs against a default-deny allowlist

It does NOT:

  • ❌ Support Discord, Slack, Feishu, QQ, or any IM other than Telegram
  • ❌ Handle images, voice, or file uploads (text only)
  • ❌ Stream replies token-by-token (Kimi emits per-turn JSON, bridge sends per-turn)
  • ❌ Expose Kimi's internal tool calls or ask for permission before they run
  • ❌ Sync state between machines (one bridge per host)
  • ❌ Multi-user (architecturally single-user — by design, not laziness)

If you need any of these, this bridge is the wrong tool.


⚙️ Configuration

The full reference lives in docs/operations.md. The minimum to know:

FieldRequiredPurpose
telegram.bot_tokenyesThe string from BotFather.
telegram.allowed_user_idsyesWhitelist of Telegram user IDs. Empty = nobody can talk to the bot (default-deny).
telegram.allowed_chat_idsrecommendedOptional chat-level whitelist. Set to your DM's chat id (= your user id) so the bot won't respond inside groups.
kimi.default_workdirnoWhere Kimi runs. Defaults to Kimi's own default.
kimi.modelnoEmpty = Kimi's default model.

config.json is gitignored. Don't commit your token.


📚 Documentation

DocumentRead this when...
docs/deployment.mdYou're installing for the first time.
docs/operations.mdYou're running the bridge day-to-day, or troubleshooting.
docs/design.mdYou want to understand why the architecture looks the way it does.
docs/security-scan.mdYou want the formal pre-publication audit findings.
SECURITY.mdYou found a vulnerability or want the security policy.
CONTRIBUTING.mdYou want to send a patch.

🤝 Contributing

PRs welcome. Please read CONTRIBUTING.md and run the tests:

uv venv .venv --python 3.11
uv pip install -e ".[dev]"
.venv/bin/pytest -v

The full suite runs in under 2 seconds and is the gate for CI.


📜 License

MIT — see LICENSE.


Built for one user, one host, one Telegram chat. Not trying to be more than that.

Report an issue · Security policy · Discussions

Collected info

  • 1 stars
  • Language: Python
  • Source updated: 7/28/2026

Config for your environment

Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.

Tool

OS

Config file: ~/.cursor/mcp.json

{
  "mcpServers": {
    "mcp-server": {
      "url": "{MCP_ENDPOINT_URL}"
    }
  }
}

Paste into mcpServers in the config file. Restart Cursor after saving.

If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.