Added detailed logging throughout the Claude spawning and output callback chain to diagnose why spawned Claude process output is logged but not sent to Telegram. Changes: - claude-spawner.ts: Added debug logs when onOutput callback is invoked, with try-catch error handling - claude-spawner.ts: Added warning when no callback is provided - index.ts: Added debug logs in all three spawn locations (/spawn command, auto-spawn, HTTP /spawn endpoint) - index.ts: Added logging to show chatId availability and callback creation - index.ts: Added detailed logging inside callbacks to trace Telegram sendMessage calls This will help identify if the issue is: 1. Callback not being passed to spawner 2. Callback not being invoked by spawner 3. chatId not being set 4. Telegram API call failing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
InnerVoice
Your inner thoughts, delivered to Telegram
MCP Server for Two-Way Communication with Claude Code and any app
This is a proper Model Context Protocol (MCP) server that enables any Claude instance to communicate with you via Telegram. Just grant Claude access to this MCP server, and it can send notifications, ask questions, and receive messages from you in real-time!
🆕 New to MCP servers? Start with the Beginner's Guide - zero experience required!
Free to use, share, and modify! See LICENSE for details.
Why This Exists
After trying email, SMS, and Google Chat integrations, Telegram emerged as the best solution for:
- ✅ Standardized MCP Integration - Works with any Claude instance automatically
- ✅ Instant two-way communication
- ✅ Free and reliable
- ✅ Works on all devices
- ✅ Simple setup
- ✅ No carrier dependencies
Features
Core Communication
- 💬 Two-Way Communication - Send messages to Claude, get responses back
- ❓ Question/Answer Flow - Claude can ask you questions and wait for answers
- 🔔 Priority Notifications - Different icons for info, success, warning, error, question
- 🌐 HTTP API - Easy integration from any app/project
- 🚀 Background Service - Runs independently, always available
- 🔧 MCP Protocol - Works as a standard MCP server in any Claude project
Multi-Project Support
- 📁 Project Context - All messages show which project they're from
- 🎯 Targeted Messages - Send messages to specific projects:
ProjectName: your message - 📊 Session Tracking - Monitor active Claude sessions across projects
- 🔄 Auto-Session Registration - Projects auto-register when Claude starts
Message Queue System
- 📬 Offline Queuing - Messages queue when projects are offline
- 📥 Persistent Storage - Queued messages survive restarts
- ⏰ Auto-Delivery - Messages delivered when Claude starts in that project
- 🧹 Auto-Cleanup - Old messages expire automatically
Remote Claude Spawner
- 🚀 Remote Spawning - Start Claude in any project from Telegram
- 📝 Project Registry - Register projects for easy remote access
- 🔄 Auto-Spawn - Optional auto-start when messages arrive
- 💀 Process Management - Track and kill spawned Claude instances
- 🎯 Initial Prompts - Start Claude with a specific task
How It Works
This is a standard MCP server that works like any other MCP tool. Once installed and configured:
- Bridge runs as a background service (connects to Telegram)
- MCP server is auto-started by Claude when needed
- Claude discovers 5 tools automatically
- You communicate via Telegram in real-time
Quick Start
1. Create Your Telegram Bot
- Open Telegram and search for
@BotFather - Send
/newbot - Follow the prompts:
- Choose a name for your bot (e.g., "My Claude Bridge")
- Choose a username (e.g., "my_claude_bridge_bot")
- Save your bot token - BotFather will give you a token like:
1234567890:ABCdefGHIjklMNOpqrsTUVwxyz - Find your bot in Telegram using the username you created
2. Install and Configure
# Clone or download this repo
cd innervoice
# Install dependencies
pnpm install
# The .env file is created automatically!
# Just edit it and add your bot token
# TELEGRAM_BOT_TOKEN=your_token_here
Edit .env:
TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID= # Leave empty - auto-set on first use
PORT=3456
HOST=localhost
ENABLED=true
3. Build and Start
# Build the project
pnpm build
# Start the bridge service
pnpm dev
# Or run as background daemon
pnpm daemon
4. Initialize Your Bot
- Open Telegram and find your bot
- Send
/startto your bot - The bot will reply and save your chat ID automatically
- Test with
/statusto verify it's working
5. Add MCP Server to Claude
✨ Auto-Start Feature: The MCP server now automatically starts the Telegram bridge when needed - no need to run it separately!
Option A: Claude Code CLI (Recommended)
Note: Using
claude mcp addautomatically adds the server globally (available in all projects). This is the recommended approach.
For Mac and Linux:
# Navigate to the innervoice directory
cd /path/to/innervoice
# Add the MCP server globally (available in all projects)
claude mcp add --transport stdio telegram \
--env TELEGRAM_BRIDGE_URL=http://localhost:3456 \
-- node "$(pwd)/dist/mcp-server.js"
# Verify it was added globally
claude mcp list
For Windows (PowerShell):
# Navigate to the innervoice directory
cd C:\path\to\innervoice
# Add the MCP server globally
claude mcp add --transport stdio telegram `
--env TELEGRAM_BRIDGE_URL=http://localhost:3456 `
-- node "$((Get-Location).Path)\dist\mcp-server.js"
# Verify it was added
claude mcp list
For Windows (Command Prompt):
# Navigate to the innervoice directory
cd C:\path\to\innervoice
# Add the MCP server globally
claude mcp add --transport stdio telegram --env TELEGRAM_BRIDGE_URL=http://localhost:3456 -- node "%CD%\dist\mcp-server.js"
# Verify it was added
claude mcp list
Option B: Manual Config File Setup
Add to your Claude Code MCP settings:
Mac/Linux: ~/.claude.json
Windows: %USERPROFILE%\.claude.json
{
"mcpServers": {
"telegram": {
"command": "node",
"args": [
"/ABSOLUTE/PATH/TO/innervoice/dist/mcp-server.js"
],
"env": {
"TELEGRAM_BRIDGE_URL": "http://localhost:3456"
}
}
}
}
Find your absolute path:
Mac/Linux:
cd innervoice && pwd
# Use output: <result>/dist/mcp-server.js
Windows (PowerShell):
cd innervoice
(Get-Location).Path
# Use output: <result>\dist\mcp-server.js
Windows (Command Prompt):
cd innervoice
cd
# Use output: <result>\dist\mcp-server.js
6. Available Tools
Once configured, Claude can automatically use:
telegram_notify- Send notifications with project contexttelegram_ask- Ask questions and wait for answerstelegram_get_messages- Check for messages from youtelegram_reply- Reply to your messagestelegram_check_health- Check bridge statustelegram_toggle_afk- Toggle AFK mode (enable/disable notifications)telegram_check_queue- Check for queued messages on startup
View detailed tool info:
pnpm tools
# or
node scripts/list-tools.js
7. AFK Mode - Control Your Notifications
Use the /afk slash command to toggle notifications on/off:
# In Claude Code, just type:
/afk
This is perfect for:
- Enabling when you step away from your computer (get notified via Telegram)
- Disabling when you're actively working (no interruptions)
The toggle state is preserved while the bridge is running, and you'll get a Telegram message confirming each change.
Optional: Install Permission Notification Hook
By default, AFK mode only sends notifications when Claude explicitly uses notification tools. If you want to receive Telegram alerts when permission prompts appear (so you know Claude is waiting for approval), install the permission hook:
Recommended: Install Globally (works in all projects)
cd /path/to/innervoice
./scripts/install-hook.sh --global
Or install per-project:
# Install in a specific project
./scripts/install-hook.sh /path/to/your/project
# Or install in current directory
cd /path/to/your/project
/path/to/innervoice/scripts/install-hook.sh
This will send you a Telegram message like:
⏸️ Claude needs permission Tool:
BashAction: Check scraped sets files Check your terminal to approve or deny.
To uninstall:
- Global:
rm ~/.claude/hooks/PermissionRequest.sh - Per-project:
rm .claude/hooks/PermissionRequest.sh
8. Verify Global Setup
After adding the MCP server, verify it's available globally:
# Check that telegram appears in the list
claude mcp list
# Should show:
# ✓ telegram: Connected
Troubleshooting: If telegram only appears in one project but not others, it was added to a project-specific config instead of globally. To fix:
- Open
~/.claude.jsonin your editor - Find the
telegramserver config underprojects→your-project-path→mcpServers - Move it to the root-level
mcpServerssection (same level as other global servers) - Remove the
telegramentry from the project-specific section
Example structure:
{
"mcpServers": {
"telegram": {
"type": "stdio",
"command": "node",
"args": ["/path/to/innervoice/dist/mcp-server.js"],
"env": {"TELEGRAM_BRIDGE_URL": "http://localhost:3456"}
}
},
"projects": {
"/your/project/path": {
"mcpServers": {} // Should be empty or not contain telegram
}
}
}
9. Test It
Restart Claude Code, then tell Claude:
"Send me a test notification via Telegram"
Claude will automatically discover and use the telegram_notify tool!
Usage Scenarios
InnerVoice supports multiple usage patterns depending on your workflow:
Scenario 1: Single Active Project
Use Case: You're actively working in one project
How it works:
- Start Claude Code in your project
- Claude auto-registers its session
- All messages go to the active session
- Messages show project context:
📁 MyProject [#abc1234]
Example:
You in Telegram: "Check the test status"
Bot: 💬 Message received - responding...
Claude: "Running tests... ✅ All 42 tests passed!"
Scenario 2: Multiple Active Projects
Use Case: Working across multiple projects simultaneously
How it works:
- Start Claude in multiple projects (each auto-registers)
- Send targeted messages:
ProjectName: your message - View active sessions with
/sessions - Each response shows its project context
Example:
You: "/sessions"
Bot: Active Claude Sessions (3)
1. 🟢 ESO-MCP [#abc1234]
Last active: 2m ago
2. 🟢 InnerVoice [#def5678]
Last active: 5m ago
3. 🟢 MyApp [#ghi9012]
Last active: 1m ago
You: "ESO-MCP: run the scraper"
Bot: 💬 Message sent to active session: ESO-MCP
Claude in ESO-MCP: 📁 ESO-MCP [#abc1234]
✅ Scraper started...
Scenario 3: Offline Project Queuing
Use Case: Send work to a project before Claude is running
How it works:
- Send:
ProjectName: your task - If project offline, message queues automatically
- Start Claude in that project
- Claude checks queue on startup and processes tasks
Example:
You: "MyApp: fix the login bug"
Bot: 📥 Message queued for MyApp (offline)
It will be delivered when Claude starts in that project.
[Later, you start Claude in MyApp]
Claude: 📬 You have 1 queued message:
1. From Richard (2:30 PM)
fix the login bug
These messages were sent while you were offline.
[Claude proceeds to work on the task]
Scenario 4: Remote Claude Spawning
Use Case: Start work remotely without opening your terminal
Setup:
# Register your projects once
You: "/register MyApp ~/code/myapp"
Bot: ✅ Project registered successfully!
📁 MyApp
📍 /Users/you/code/myapp
⏸️ Manual spawn only
Spawn with: /spawn MyApp
Daily Usage:
You: "/spawn MyApp Fix the login bug"
Bot: ⏳ Starting Claude in MyApp...
✅ Claude started in MyApp with prompt: "Fix the login bug"
PID: 12345
You can now send messages to it: MyApp: your message
[Claude automatically starts working on the bug]
Claude: 📁 MyApp [#abc1234]
🔍 Analyzing login flow...
✅ Bug fixed! The session timeout was too short.
Scenario 5: Auto-Spawn Projects
Use Case: Projects that should start automatically when messaged
Setup:
You: "/register MyApp ~/code/myapp --auto-spawn"
Bot: ✅ Project registered successfully!
📁 MyApp
📍 /Users/you/code/myapp
🔄 Auto-spawn enabled
Daily Usage:
You: "MyApp: run the tests"
Bot: 🚀 Auto-spawning Claude in MyApp...
✅ Claude started in MyApp
PID: 12345
[Claude auto-starts and processes the message]
Claude: 📁 MyApp [#abc1234]
🧪 Running test suite...
✅ All 42 tests passed!
Scenario 6: Managing Multiple Projects
View all projects:
You: "/projects"
Bot: Registered Projects (4)
1. 🟢 ESO-MCP 🔄
📍 /Users/you/code/eso-mcp
🕐 Last: 12/23/2025
2. ⚪ InnerVoice ⏸️
📍 /Users/you/code/innervoice
🕐 Last: 12/22/2025
3. 🟢 MyApp 🔄
📍 /Users/you/code/myapp
🕐 Last: 12/23/2025
4. ⚪ TestProject ⏸️
📍 /Users/you/code/test
🕐 Last: 12/20/2025
🟢 Running ⚪ Offline 🔄 Auto-spawn ⏸️ Manual
Check running processes:
You: "/spawned"
Bot: Spawned Claude Processes (2)
1. ESO-MCP
🆔 PID: 12345
⏱️ Running: 15m
💬 "run the scraper"
2. MyApp
🆔 PID: 12346
⏱️ Running: 5m
Kill with: /kill ProjectName
Stop a project:
You: "/kill MyApp"
Bot: 🛑 ✅ Claude process terminated in MyApp
Telegram Bot Commands
Complete list of available bot commands:
Session Management
/sessions- List all active Claude sessions with status/queue- View queued messages for offline projects
Project Management
/projects- List all registered projects with status/register ProjectName /path [--auto-spawn]- Register a new project/unregister ProjectName- Remove a project from registry/spawn ProjectName [prompt]- Start Claude in a project/spawned- List all running spawned Claude processes/kill ProjectName- Terminate a spawned Claude process
Bot Control
/start- Initialize bot and save your chat ID/help- Show all available commands/status- Check bridge health and status/test- Send a test notification
Message Syntax
- Regular message: Goes to active Claude (if only one running)
ProjectName: message- Send to specific project- If project offline, message automatically queues
MCP Tools Reference
Once configured, Claude can automatically use these tools:
telegram_notify
Send a notification to you via Telegram.
Parameters:
message(required): The notification text (supports Markdown)priority(optional):info|success|warning|error|question
Example Claude Usage:
"I've completed the database migration. Let me notify you." Claude uses:
telegram_notify({ message: "Database migration complete!", priority: "success" })
telegram_ask
Ask you a question and wait for your answer (blocking).
Parameters:
question(required): The question to ask (supports Markdown)timeout(optional): Milliseconds to wait (default: 300000 = 5 min)
Example Claude Usage:
"Should I deploy to production? Let me ask you." Claude uses:
telegram_ask({ question: "Deploy to production now?" })Waits for your response via Telegram
telegram_get_messages
Check for unread messages from you.
Example Claude Usage:
"Let me check if you've sent any messages." Claude uses:
telegram_get_messages({})
telegram_reply
Reply to your message via Telegram.
Parameters:
message(required): Your reply (supports Markdown)
Example Claude Usage:
"I'll respond to your question via Telegram." Claude uses:
telegram_reply({ message: "The build succeeded!" })
telegram_check_health
Check if the Telegram bridge is running and healthy.
Example Claude Usage:
"Let me verify the Telegram bridge is working." Claude uses:
telegram_check_health({})
telegram_toggle_afk
Toggle AFK (Away From Keyboard) mode - enables or disables Telegram notifications.
No parameters required
Example Claude Usage:
"Toggle AFK mode" Claude uses:
telegram_toggle_afk({})
When to use:
- Enable when stepping away from your computer (get notifications)
- Disable when actively working (avoid interruptions)
- State is preserved while the bridge is running
telegram_check_queue
Check if there are queued messages for this project from when Claude was offline.
No parameters required
Example Claude Usage:
"On startup, let me check for any queued messages." Claude uses:
telegram_check_queue({})
Returns:
- List of messages sent while Claude was offline
- Includes sender, timestamp, and message content
- Messages are marked as delivered after retrieval
When to use:
- On startup to catch up on offline messages
- Proactively check for pending work
- After long idle periods
Git Setup (For Sharing)
If you want to push this to your own Git repository:
# Initialize git (if not already done)
git init
# Add all files (gitignore protects secrets)
git add .
# Commit
git commit -m "Initial commit: Telegram MCP server"
# Add your remote
git remote add origin https://github.com/yourusername/innervoice.git
# Push
git push -u origin main
What's Safe to Share:
- ✅ All source code
- ✅
.env.example(template) - ✅ Documentation
- ✅ Configuration templates
What's Protected (in .gitignore):
- 🔒
.env(your bot token and secrets) - 🔒
node_modules/ - 🔒
dist/
For Others Cloning Your Repository
When someone clones your repo, they need to:
- Create their own Telegram bot with @BotFather
- Copy the template:
cp .env.example .env - Add their bot token to
.env - Install and build:
pnpm install && pnpm build - Follow the Quick Start guide above
Legacy HTTP API (For Direct Integration)
If you want to use the HTTP API directly (without MCP), you can:
// Simple notification
await fetch('http://localhost:3456/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: 'Scraping complete! Found 500 skills.',
priority: 'success'
})
});
// Question with markdown
await fetch('http://localhost:3456/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: '*Question:*\nContinue scraping sets?\n\nReply: yes/no',
priority: 'question',
parseMode: 'Markdown'
})
});
Priority Levels
info- ℹ️ General informationsuccess- ✅ Task completedwarning- ⚠️ Warning messageerror- ❌ Error occurredquestion- ❓ Needs your input
How Communication Works
Basic Message Flow
- Send any message to the bot in Telegram
- Bot acknowledges with "💬 Message received - responding..."
- Claude checks messages and responds when available
- You get the response in Telegram with project context
Targeted Messages
Send ProjectName: your message to communicate with a specific project:
- If project is running: Message delivered immediately
- If project is offline: Message queues automatically
Notifications
Claude sends you updates via the telegram_notify tool with:
- Project context:
📁 ProjectName [#abc1234] - Priority icons: ℹ️ ✅ ⚠️ ❌ ❓
- Markdown formatting support
Questions
- Claude sends a question via
telegram_ask - You see "❓ [question]" in Telegram
- Your next message is automatically treated as the answer
- Claude receives your answer and continues
Queued Messages
- Send message to offline project
- Message queues persistently
- When Claude starts in that project, it checks the queue
- Queued messages are delivered and processed
Running as Background Service
# Build production version
pnpm build
# Start as daemon (requires pm2)
npm install -g pm2
pnpm daemon
# Check logs
pnpm logs
# Stop daemon
pnpm stop
API Endpoints
POST /notify
Send a notification to user
Request:
{
"message": "Your notification text",
"priority": "info|success|warning|error|question",
"parseMode": "Markdown|HTML"
}
Response:
{
"success": true,
"chatId": "7684777367"
}
GET /messages
Get unread messages from user
Response:
{
"messages": [
{
"from": "Richard",
"message": "What's the status?",
"timestamp": "2025-11-23T04:00:52.395Z",
"read": false
}
],
"count": 1
}
POST /messages/read
Mark messages as read
Request:
{
"count": 2 // optional, marks all if not provided
}
Response:
{
"markedAsRead": 2
}
POST /reply
Send a reply to user's message
Request:
{
"message": "Here's my response to your question"
}
Response:
{
"success": true
}
POST /ask
Ask user a question and wait for answer (blocking)
Request:
{
"question": "Should I continue scraping?",
"timeout": 300000 // optional, 5 min default
}
Response:
{
"answer": "yes"
}
GET /health
Check service health
Response:
{
"status": "running",
"enabled": true,
"chatId": "set",
"unreadMessages": 0,
"pendingQuestions": 0
}
POST /toggle
Toggle notifications on/off (AFK mode)
Response:
{
"success": true,
"enabled": true,
"previousState": false,
"message": "🟢 InnerVoice notifications ENABLED - You will receive messages"
}
GET /status
Get current notification state
Response:
{
"enabled": true,
"message": "Notifications are ON"
}
Session Management Endpoints
POST /session/register
Register or update a Claude session
Request:
{
"sessionId": "unique-session-id",
"projectName": "MyProject",
"projectPath": "/path/to/project"
}
Response:
{
"success": true,
"sessionId": "unique-session-id",
"projectName": "MyProject"
}
GET /sessions
List all active Claude sessions
Response:
{
"sessions": [
{
"id": "1234567-abc",
"projectName": "MyProject",
"projectPath": "/path/to/project",
"startTime": "2025-11-23T10:00:00.000Z",
"lastActivity": "2025-11-23T10:30:00.000Z",
"status": "active",
"idleMinutes": 5
}
],
"count": 1
}
Queue Management Endpoints
GET /queue/:projectName
Get pending messages for a project
Response:
{
"projectName": "MyProject",
"tasks": [
{
"id": "task-123",
"projectName": "MyProject",
"message": "Fix the bug",
"from": "Richard",
"timestamp": "2025-11-23T09:00:00.000Z",
"priority": "normal",
"status": "pending"
}
],
"count": 1
}
GET /queue/summary
Get summary of all queued messages
Response:
{
"summary": [
{
"projectName": "MyProject",
"pending": 2,
"delivered": 5,
"total": 7
}
],
"totalProjects": 1
}
Project Registry Endpoints
GET /projects
List all registered projects
Response:
{
"projects": [
{
"name": "MyProject",
"path": "/path/to/project",
"lastAccessed": "2025-11-23T10:00:00.000Z",
"autoSpawn": false,
"metadata": {
"description": "My project description",
"tags": ["web", "api"]
}
}
],
"count": 1
}
POST /projects/register
Register a new project
Request:
{
"name": "MyProject",
"path": "/path/to/project",
"autoSpawn": false,
"description": "Optional description",
"tags": ["tag1", "tag2"]
}
Response:
{
"success": true,
"project": {
"name": "MyProject",
"path": "/path/to/project",
"lastAccessed": "2025-11-23T10:00:00.000Z",
"autoSpawn": false
}
}
DELETE /projects/:name
Unregister a project
Response:
{
"success": true,
"message": "Project MyProject unregistered"
}
Claude Spawner Endpoints
POST /spawn
Spawn Claude in a registered project
Request:
{
"projectName": "MyProject",
"initialPrompt": "Optional initial task"
}
Response:
{
"success": true,
"message": "✅ Claude started in MyProject",
"pid": 12345
}
POST /kill/:projectName
Terminate a spawned Claude process
Response:
{
"success": true,
"message": "✅ Claude process terminated in MyProject"
}
GET /spawned
List all spawned Claude processes
Response:
{
"processes": [
{
"projectName": "MyProject",
"pid": 12345,
"startTime": "2025-11-23T10:00:00.000Z",
"initialPrompt": "Fix the bug",
"runningMinutes": 15
}
],
"count": 1
}
GET /spawned/:projectName
Check if Claude is running in a project
Response:
{
"running": true,
"process": {
"projectName": "MyProject",
"pid": 12345,
"runningMinutes": 15
}
}
Integration with ESO-MCP
Add this helper to your ESO-MCP project:
// src/utils/notify.ts
export async function notify(message: string, priority = 'info') {
try {
await fetch('http://localhost:3456/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, priority })
});
} catch (error) {
console.log('Telegram bridge not available');
}
}
Then use anywhere:
await notify('✅ Skills scraping complete!', 'success');
await notify('❌ Failed to scrape sets page', 'error');
Environment Variables
TELEGRAM_BOT_TOKEN=your_token_here
TELEGRAM_CHAT_ID=auto_detected
PORT=3456
HOST=localhost
ENABLED=true
Development
Want to contribute or modify the bridge? See CONTRIBUTING.md for local development setup.
License
MIT License - see LICENSE for details