npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

telegram-claude-mcp

v3.0.1

Published

Telegram bridge for Claude CLI - bidirectional communication via tmux

Readme

telegram-claude-mcp

Get Telegram notifications from Claude Code with interactive permission buttons.

When Claude needs permission to run a command or access a file, you'll get a Telegram message with Allow/Deny buttons. When Claude finishes working, you can reply with more instructions to continue.

Quick Start

npx telegram-claude-setup

Then:

  1. Create a Telegram bot via @BotFather
  2. Get your chat ID (see instructions below)
  3. Edit ~/.claude/settings.json with your bot token and chat ID
  4. Restart Claude Code

Features

  • Permission buttons - Allow/Deny tool usage from Telegram
  • Interactive stop - Reply to continue Claude's work after it stops
  • Auto-retry with reminders - Get reminders every 2 minutes if you miss a message
  • Notifications - Get notified about Claude events
  • Multi-session - Run multiple Claude instances with message tagging

Manual Installation

If you prefer not to use npm, follow these steps:

1. Create Hook Scripts

Create directory ~/.claude/hooks/ and add these scripts:

~/.claude/hooks/permission-hook.sh

#!/bin/bash
# Set SESSION_NAME env var to target a specific session (for multi-session setups)
SESSION_DIR="/tmp/telegram-claude-sessions"

find_session() {
  if [ -n "$SESSION_NAME" ]; then
    local f="$SESSION_DIR/${SESSION_NAME}.info"
    [ -f "$f" ] && { local p=$(jq -r '.pid // empty' "$f" 2>/dev/null); [ -n "$p" ] && kill -0 "$p" 2>/dev/null && echo "$f"; return; }
    return
  fi
  local latest="" latest_time=0
  [ -d "$SESSION_DIR" ] || return
  for f in "$SESSION_DIR"/*.info; do
    [ -e "$f" ] || continue
    local p=$(jq -r '.pid // empty' "$f" 2>/dev/null)
    [ -n "$p" ] && kill -0 "$p" 2>/dev/null && {
      local t=$(stat -f %m "$f" 2>/dev/null || stat -c %Y "$f" 2>/dev/null)
      [ "$t" -gt "$latest_time" ] && { latest_time=$t; latest=$f; }
    }
  done
  echo "$latest"
}

INFO_FILE=$(find_session)
[ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0

HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
HOOK_URL="http://${HOOK_HOST}:${HOOK_PORT}/permission"

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .toolName // .name // empty')
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // .toolInput // .input // .arguments // {}')

PAYLOAD=$(jq -n --arg tool_name "$TOOL_NAME" --argjson tool_input "$TOOL_INPUT" \
  '{tool_name: $tool_name, tool_input: $tool_input}')

RESPONSE=$(curl -s -X POST "$HOOK_URL" -H "Content-Type: application/json" -d "$PAYLOAD" --max-time 600)
[ $? -eq 0 ] && echo "$RESPONSE"

~/.claude/hooks/stop-hook.sh

#!/bin/bash
# Set SESSION_NAME env var to target a specific session (for multi-session setups)
SESSION_DIR="/tmp/telegram-claude-sessions"

find_session() {
  if [ -n "$SESSION_NAME" ]; then
    local f="$SESSION_DIR/${SESSION_NAME}.info"
    [ -f "$f" ] && { local p=$(jq -r '.pid // empty' "$f" 2>/dev/null); [ -n "$p" ] && kill -0 "$p" 2>/dev/null && echo "$f"; return; }
    return
  fi
  local latest="" latest_time=0
  [ -d "$SESSION_DIR" ] || return
  for f in "$SESSION_DIR"/*.info; do
    [ -e "$f" ] || continue
    local p=$(jq -r '.pid // empty' "$f" 2>/dev/null)
    [ -n "$p" ] && kill -0 "$p" 2>/dev/null && {
      local t=$(stat -f %m "$f" 2>/dev/null || stat -c %Y "$f" 2>/dev/null)
      [ "$t" -gt "$latest_time" ] && { latest_time=$t; latest=$f; }
    }
  done
  echo "$latest"
}

INPUT=$(cat)
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
[ "$STOP_HOOK_ACTIVE" = "true" ] && exit 0

INFO_FILE=$(find_session)
[ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0

HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
HOOK_URL="http://${HOOK_HOST}:${HOOK_PORT}/stop"

TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
PAYLOAD=$(jq -n --arg transcript_path "$TRANSCRIPT_PATH" '{transcript_path: $transcript_path}')

RESPONSE=$(curl -s -X POST "$HOOK_URL" -H "Content-Type: application/json" -d "$PAYLOAD" --max-time 300)
if [ $? -eq 0 ]; then
  DECISION=$(echo "$RESPONSE" | jq -r '.decision // empty')
  [ "$DECISION" = "block" ] && echo "$RESPONSE"
fi

~/.claude/hooks/notify-hook.sh

#!/bin/bash
# Set SESSION_NAME env var to target a specific session (for multi-session setups)
SESSION_DIR="/tmp/telegram-claude-sessions"

find_session() {
  if [ -n "$SESSION_NAME" ]; then
    local f="$SESSION_DIR/${SESSION_NAME}.info"
    [ -f "$f" ] && { local p=$(jq -r '.pid // empty' "$f" 2>/dev/null); [ -n "$p" ] && kill -0 "$p" 2>/dev/null && echo "$f"; return; }
    return
  fi
  local latest="" latest_time=0
  [ -d "$SESSION_DIR" ] || return
  for f in "$SESSION_DIR"/*.info; do
    [ -e "$f" ] || continue
    local p=$(jq -r '.pid // empty' "$f" 2>/dev/null)
    [ -n "$p" ] && kill -0 "$p" 2>/dev/null && {
      local t=$(stat -f %m "$f" 2>/dev/null || stat -c %Y "$f" 2>/dev/null)
      [ "$t" -gt "$latest_time" ] && { latest_time=$t; latest=$f; }
    }
  done
  echo "$latest"
}

INFO_FILE=$(find_session)
[ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0

HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
HOOK_URL="http://${HOOK_HOST}:${HOOK_PORT}/notify"

INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Notification"')
PAYLOAD=$(jq -n --arg type "notification" --arg message "$MESSAGE" '{type: $type, message: $message}')

curl -s -X POST "$HOOK_URL" -H "Content-Type: application/json" -d "$PAYLOAD" --max-time 10 >/dev/null 2>&1

Make them executable:

chmod +x ~/.claude/hooks/*.sh

2. Configure Claude Settings

Add to ~/.claude/settings.json:

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "*",
        "hooks": [{ "type": "command", "command": "SESSION_NAME=default ~/.claude/hooks/permission-hook.sh" }]
      }
    ],
    "Stop": [
      {
        "matcher": "*",
        "hooks": [{ "type": "command", "command": "SESSION_NAME=default ~/.claude/hooks/stop-hook.sh" }]
      }
    ],
    "Notification": [
      {
        "matcher": "*",
        "hooks": [{ "type": "command", "command": "SESSION_NAME=default ~/.claude/hooks/notify-hook.sh" }]
      }
    ]
  },
  "mcpServers": {
    "telegram": {
      "command": "npx",
      "args": ["-y", "telegram-claude-mcp"],
      "env": {
        "TELEGRAM_BOT_TOKEN": "YOUR_BOT_TOKEN",
        "TELEGRAM_CHAT_ID": "YOUR_CHAT_ID",
        "SESSION_NAME": "default"
      }
    }
  }
}

Important: The SESSION_NAME in hook commands must match the SESSION_NAME in the MCP server env.

3. Create Telegram Bot

  1. Open Telegram and message @BotFather
  2. Send /newbot and follow prompts
  3. Copy the bot token

4. Get Your Chat ID

  1. Start a chat with your new bot
  2. Send any message to it
  3. Visit: https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates
  4. Find "chat":{"id": YOUR_CHAT_ID} in the response

5. Update Settings

Replace YOUR_BOT_TOKEN and YOUR_CHAT_ID in ~/.claude/settings.json

6. Restart Claude Code

Environment Variables

| Variable | Description | Default | |----------|-------------|---------| | TELEGRAM_BOT_TOKEN | Bot token from @BotFather | Required | | TELEGRAM_CHAT_ID | Your Telegram chat ID | Required | | SESSION_NAME | Session identifier (for multi-instance) | "default" | | SESSION_PORT | Preferred HTTP port (auto-finds if busy) | 3333 | | CHAT_RESPONSE_TIMEOUT_MS | Timeout for message responses | 600000 (10 min) | | PERMISSION_TIMEOUT_MS | Timeout for permission decisions | 600000 (10 min) |

How It Works

Claude Code                    Hook Scripts                 telegram-claude-mcp
    |                              |                              |
    |-- Permission needed -------->|                              |
    |                              |-- POST /permission --------->|
    |                              |                              |-- Send to Telegram
    |                              |                              |<- User clicks Allow
    |                              |<-------- {allow} ------------|
    |<-------- Allow --------------|                              |
    |                              |                              |
    |-- Work complete, stops ----->|                              |
    |                              |-- POST /stop --------------->|
    |                              |                              |-- Send to Telegram
    |                              |                              |<- User replies
    |                              |<-- {block, "do X next"} -----|
    |<-- Continue with "do X" -----|                              |

Multiple Sessions

Run multiple Claude Code instances (e.g., ~/.claude and ~/.claude-personal) with proper message routing.

Why This Matters

Without proper configuration, hooks from one Claude session might route to another session's MCP server. The SESSION_NAME environment variable ensures each session connects to its own MCP server.

Configuration

Each Claude config needs a unique SESSION_NAME in both the MCP server env and the hook commands.

~/.claude/settings.json (main):

{
  "hooks": {
    "PermissionRequest": [{
      "matcher": "*",
      "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks/permission-hook.sh" }]
    }],
    "Stop": [{
      "matcher": "*",
      "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks/stop-hook.sh" }]
    }],
    "Notification": [{
      "matcher": "*",
      "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks/notify-hook.sh" }]
    }]
  },
  "mcpServers": {
    "telegram": {
      "command": "npx",
      "args": ["-y", "telegram-claude-mcp"],
      "env": {
        "TELEGRAM_BOT_TOKEN": "YOUR_BOT_TOKEN",
        "TELEGRAM_CHAT_ID": "YOUR_CHAT_ID",
        "SESSION_NAME": "main"
      }
    }
  }
}

~/.claude-personal/settings.json (personal):

{
  "hooks": {
    "PermissionRequest": [{
      "matcher": "*",
      "hooks": [{ "type": "command", "command": "SESSION_NAME=personal ~/.claude/hooks/permission-hook.sh" }]
    }],
    "Stop": [{
      "matcher": "*",
      "hooks": [{ "type": "command", "command": "SESSION_NAME=personal ~/.claude/hooks/stop-hook.sh" }]
    }],
    "Notification": [{
      "matcher": "*",
      "hooks": [{ "type": "command", "command": "SESSION_NAME=personal ~/.claude/hooks/notify-hook.sh" }]
    }]
  },
  "mcpServers": {
    "telegram": {
      "command": "npx",
      "args": ["-y", "telegram-claude-mcp"],
      "env": {
        "TELEGRAM_BOT_TOKEN": "YOUR_BOT_TOKEN",
        "TELEGRAM_CHAT_ID": "YOUR_CHAT_ID",
        "SESSION_NAME": "personal"
      }
    }
  }
}

Key Points

  • SESSION_NAME must match - The env var in hook commands must match the MCP server's SESSION_NAME
  • Share hook scripts - All configs can use the same hook scripts in ~/.claude/hooks/
  • Message tagging - Messages are tagged with [main] or [personal] so you know the source
  • Separate ports - Each MCP server auto-discovers an available port

Troubleshooting

Buttons appear but don't work:

  • Check for stale MCP processes: ps aux | grep telegram-claude
  • Kill old processes and restart Claude

No messages in Telegram:

  • Verify bot token and chat ID are correct
  • Ensure you've started a chat with your bot
  • Check Claude's MCP server logs

Permission hook not firing:

  • Verify hook scripts are executable: ls -la ~/.claude/hooks/
  • Check Claude settings have hooks configured

v2: Daemon Architecture (Recommended for Multiple Sessions)

Version 2 introduces a singleton daemon architecture that handles all Telegram communication from one process. This solves:

  • Multiple bot polling instances causing rate limiting
  • Complex port discovery between sessions
  • Session state synchronization issues

See ARCHITECTURE.md for detailed design documentation.

Quick Start with Daemon

Option A: Auto-start (recommended)

The proxy can auto-start the daemon if you provide credentials in the MCP config:

{
  "mcpServers": {
    "telegram": {
      "command": "npx",
      "args": ["-y", "telegram-claude-proxy"],
      "env": {
        "SESSION_NAME": "main",
        "TELEGRAM_BOT_TOKEN": "YOUR_BOT_TOKEN",
        "TELEGRAM_CHAT_ID": "YOUR_CHAT_ID"
      }
    }
  }
}

The first Claude session will start the daemon automatically. Subsequent sessions connect to the already-running daemon.

Option B: Manual daemon start

Start the daemon once, then proxies connect without needing credentials:

# Start daemon with credentials
TELEGRAM_BOT_TOKEN=xxx TELEGRAM_CHAT_ID=yyy telegram-claude-ctl start

# Check status
telegram-claude-ctl status
  1. Configure Claude to use the proxy in ~/.claude/settings.json:
{
  "hooks": {
    "PermissionRequest": [{
      "matcher": "*",
      "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks-v2/permission-hook.sh" }]
    }],
    "Stop": [{
      "matcher": "*",
      "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks-v2/stop-hook.sh" }]
    }],
    "Notification": [{
      "matcher": "*",
      "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks-v2/notify-hook.sh" }]
    }]
  },
  "mcpServers": {
    "telegram": {
      "command": "npx",
      "args": ["-y", "telegram-claude-proxy"],
      "env": {
        "SESSION_NAME": "main"
      }
    }
  }
}

Daemon Commands

# Start daemon in background
telegram-claude-ctl start

# Stop daemon
telegram-claude-ctl stop

# Restart daemon
telegram-claude-ctl restart

# Check status (shows active sessions)
telegram-claude-ctl status

Architecture Overview

┌─────────────────┐     stdio      ┌─────────────────┐     Unix Socket     ┌─────────────────────┐
│  Claude Code 1  │◄──────────────►│  MCP Proxy 1    │◄───────────────────►│                     │
└─────────────────┘                └─────────────────┘                     │  Singleton Daemon   │
                                                                           │                     │
┌─────────────────┐     stdio      ┌─────────────────┐     Unix Socket     │  - Single bot poll  │──► Telegram
│  Claude Code 2  │◄──────────────►│  MCP Proxy 2    │◄───────────────────►│  - Session manager  │
└─────────────────┘                └─────────────────┘                     │  - HTTP hooks :3333 │
                                                                           └─────────────────────┘

Environment Variables (Daemon)

| Variable | Description | Default | |----------|-------------|---------| | TELEGRAM_BOT_TOKEN | Bot token from @BotFather | Required | | TELEGRAM_CHAT_ID | Your Telegram chat ID | Required | | CHAT_RESPONSE_TIMEOUT_MS | Response timeout | 600000 (10 min) | | PERMISSION_TIMEOUT_MS | Permission timeout | 600000 (10 min) |

Environment Variables (Proxy/Hooks)

| Variable | Description | Default | |----------|-------------|---------| | SESSION_NAME | Session identifier | Auto-detected from CWD | | TELEGRAM_CLAUDE_PORT | Daemon HTTP port | 3333 | | TELEGRAM_CLAUDE_HOST | Daemon host | localhost |

License

MIT