feat: add auto-start, AFK mode, and comprehensive cross-platform setup
Major Features: - Auto-start: MCP server now automatically starts Telegram bridge on demand - AFK Mode: Toggle notifications on/off with /afk slash command - New telegram_toggle_afk MCP tool for controlling notification state - Dynamic enable/disable via new /toggle and /status API endpoints MCP Server Improvements: - Auto-detects if bridge is running and starts it automatically - Monitors bridge process health and logs output - Clean shutdown handling for both MCP server and bridge - Process spawning with proper environment variable passing Telegram Bridge Updates: - Runtime toggle for ENABLED state (was previously static) - POST /toggle endpoint to toggle notifications with Telegram confirmation - GET /status endpoint to check current notification state - Telegram messages sent when state changes (🟢/🔴 indicators) Documentation: - Cross-platform setup instructions (Mac, Linux, Windows) - Claude Code CLI setup guide with claude mcp add commands - Global vs project-specific MCP configuration explained - Troubleshooting section for fixing configuration scope issues - Complete AFK mode usage documentation - All new API endpoints documented Slash Commands: - Created /afk command in .claude/commands/afk.md - Available in both InnerVoice and ESO-MCP projects Files Changed: - src/mcp-server.ts: Auto-start logic and telegram_toggle_afk tool - src/index.ts: Dynamic ENABLED toggle and new API endpoints - README.md: Comprehensive setup and troubleshooting guide - mcp-config.json: Updated with correct absolute path - .claude/commands/afk.md: New slash command for AFK mode 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
5
.claude/commands/afk.md
Normal file
5
.claude/commands/afk.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
description: Toggle InnerVoice AFK mode - enable/disable Telegram notifications
|
||||
---
|
||||
|
||||
Use the `telegram_toggle_afk` MCP tool to toggle InnerVoice notifications. This controls whether you receive Telegram messages while away from your system.
|
||||
168
README.md
168
README.md
@@ -101,23 +101,63 @@ pnpm daemon
|
||||
|
||||
### 5. Add MCP Server to Claude
|
||||
|
||||
#### Option A: Auto-Generate Config (Easiest)
|
||||
> **✨ 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 add` automatically adds the server **globally** (available in all projects). This is the recommended approach.
|
||||
|
||||
**For Mac and Linux:**
|
||||
```bash
|
||||
cd innervoice
|
||||
./scripts/get-mcp-config.sh
|
||||
# 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
|
||||
```
|
||||
|
||||
Copy the output to your MCP config file.
|
||||
**For Windows (PowerShell):**
|
||||
```powershell
|
||||
# Navigate to the innervoice directory
|
||||
cd C:\path\to\innervoice
|
||||
|
||||
#### Option B: Manual Setup
|
||||
# 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"
|
||||
|
||||
Add to your Claude Code MCP settings (`~/.config/claude-code/settings/mcp.json`):
|
||||
# Verify it was added
|
||||
claude mcp list
|
||||
```
|
||||
|
||||
**For Windows (Command Prompt):**
|
||||
```cmd
|
||||
# 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`
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"innervoice": {
|
||||
"telegram": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"/ABSOLUTE/PATH/TO/innervoice/dist/mcp-server.js"
|
||||
@@ -130,17 +170,27 @@ Add to your Claude Code MCP settings (`~/.config/claude-code/settings/mcp.json`)
|
||||
}
|
||||
```
|
||||
|
||||
**Find your path:**
|
||||
**Find your absolute path:**
|
||||
|
||||
**Mac/Linux:**
|
||||
```bash
|
||||
cd innervoice && pwd
|
||||
# Use output: <result>/dist/mcp-server.js
|
||||
```
|
||||
|
||||
#### MCP Config Locations
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
cd innervoice
|
||||
(Get-Location).Path
|
||||
# Use output: <result>\dist\mcp-server.js
|
||||
```
|
||||
|
||||
- **Global (all projects):** `~/.config/claude-code/settings/mcp.json`
|
||||
- **Per-project:** `your-project/.claude/mcp.json`
|
||||
- **VS Code:** `your-project/.vscode/mcp.json`
|
||||
**Windows (Command Prompt):**
|
||||
```cmd
|
||||
cd innervoice
|
||||
cd
|
||||
# Use output: <result>\dist\mcp-server.js
|
||||
```
|
||||
|
||||
### 6. Available Tools
|
||||
|
||||
@@ -150,6 +200,7 @@ Once configured, Claude can automatically use:
|
||||
- `telegram_get_messages` - Check for messages from you
|
||||
- `telegram_reply` - Reply to your messages
|
||||
- `telegram_check_health` - Check bridge status
|
||||
- `telegram_toggle_afk` - Toggle AFK mode (enable/disable notifications)
|
||||
|
||||
**View detailed tool info:**
|
||||
```bash
|
||||
@@ -158,7 +209,60 @@ pnpm tools
|
||||
node scripts/list-tools.js
|
||||
```
|
||||
|
||||
### 7. Test It
|
||||
### 7. AFK Mode - Control Your Notifications
|
||||
|
||||
Use the `/afk` slash command to toggle notifications on/off:
|
||||
|
||||
```bash
|
||||
# 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.
|
||||
|
||||
### 8. Verify Global Setup
|
||||
|
||||
After adding the MCP server, verify it's available globally:
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
1. Open `~/.claude.json` in your editor
|
||||
2. Find the `telegram` server config under `projects` → `your-project-path` → `mcpServers`
|
||||
3. **Move it** to the root-level `mcpServers` section (same level as other global servers)
|
||||
4. Remove the `telegram` entry from the project-specific section
|
||||
|
||||
**Example structure:**
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
@@ -217,6 +321,20 @@ Check if the Telegram bridge is running and healthy.
|
||||
> "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
|
||||
|
||||
## Git Setup (For Sharing)
|
||||
|
||||
If you want to push this to your own Git repository:
|
||||
@@ -443,6 +561,30 @@ Check service health
|
||||
}
|
||||
```
|
||||
|
||||
### POST /toggle
|
||||
Toggle notifications on/off (AFK mode)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"enabled": true,
|
||||
"previousState": false,
|
||||
"message": "🟢 InnerVoice notifications ENABLED - You will receive messages"
|
||||
}
|
||||
```
|
||||
|
||||
### GET /status
|
||||
Get current notification state
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"message": "Notifications are ON"
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with ESO-MCP
|
||||
|
||||
Add this helper to your ESO-MCP project:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"telegram": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"/ABSOLUTE/PATH/TO/claude-telegram-bridge/dist/mcp-server.js"
|
||||
"/Volumes/PRO-G40/Docker/innervoice/dist/mcp-server.js"
|
||||
],
|
||||
"env": {
|
||||
"TELEGRAM_BRIDGE_URL": "http://localhost:3456"
|
||||
|
||||
36
src/index.ts
36
src/index.ts
@@ -10,7 +10,7 @@ const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN!);
|
||||
const app = express();
|
||||
const PORT = parseInt(process.env.PORT || '3456');
|
||||
const HOST = process.env.HOST || 'localhost';
|
||||
const ENABLED = process.env.ENABLED !== 'false';
|
||||
let ENABLED = process.env.ENABLED !== 'false'; // Now mutable for runtime toggling
|
||||
|
||||
let chatId: string | null = process.env.TELEGRAM_CHAT_ID || null;
|
||||
const envPath = path.join(process.cwd(), '.env');
|
||||
@@ -256,6 +256,40 @@ app.get('/health', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle enabled state
|
||||
app.post('/toggle', async (req, res) => {
|
||||
const previousState = ENABLED;
|
||||
ENABLED = !ENABLED;
|
||||
|
||||
const statusMessage = ENABLED
|
||||
? '🟢 InnerVoice notifications ENABLED - You will receive messages'
|
||||
: '🔴 InnerVoice notifications DISABLED - Messages paused';
|
||||
|
||||
// Notify via Telegram if chat ID is set
|
||||
if (chatId) {
|
||||
try {
|
||||
await bot.telegram.sendMessage(chatId, statusMessage, { parse_mode: 'Markdown' });
|
||||
} catch (error) {
|
||||
console.error('Failed to send toggle notification:', error);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
enabled: ENABLED,
|
||||
previousState,
|
||||
message: statusMessage
|
||||
});
|
||||
});
|
||||
|
||||
// Get current enabled state
|
||||
app.get('/status', (req, res) => {
|
||||
res.json({
|
||||
enabled: ENABLED,
|
||||
message: ENABLED ? 'Notifications are ON' : 'Notifications are OFF (AFK mode)'
|
||||
});
|
||||
});
|
||||
|
||||
// Start bot
|
||||
bot.launch().then(() => {
|
||||
console.log('🤖 Telegram bot started');
|
||||
|
||||
@@ -7,8 +7,93 @@ import {
|
||||
ListToolsRequestSchema,
|
||||
Tool,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const BRIDGE_URL = process.env.TELEGRAM_BRIDGE_URL || 'http://localhost:3456';
|
||||
const BRIDGE_PORT = new URL(BRIDGE_URL).port || '3456';
|
||||
const BRIDGE_HOST = new URL(BRIDGE_URL).hostname || 'localhost';
|
||||
|
||||
let bridgeProcess: ChildProcess | null = null;
|
||||
|
||||
// Check if the bridge is running
|
||||
async function isBridgeRunning(): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${BRIDGE_URL}/health`, {
|
||||
signal: AbortSignal.timeout(2000),
|
||||
});
|
||||
return response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Start the Telegram bridge
|
||||
async function startBridge(): Promise<void> {
|
||||
console.error('🚀 Starting Telegram bridge...');
|
||||
|
||||
// Find the project root (one level up from dist or src)
|
||||
const projectRoot = join(__dirname, '..');
|
||||
const bridgeScript = join(projectRoot, 'dist', 'index.js');
|
||||
|
||||
// Start the bridge process
|
||||
bridgeProcess = spawn('node', [bridgeScript], {
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: BRIDGE_PORT,
|
||||
HOST: BRIDGE_HOST,
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
});
|
||||
|
||||
// Log bridge output
|
||||
if (bridgeProcess.stdout) {
|
||||
bridgeProcess.stdout.on('data', (data) => {
|
||||
console.error(`[Bridge] ${data.toString().trim()}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (bridgeProcess.stderr) {
|
||||
bridgeProcess.stderr.on('data', (data) => {
|
||||
console.error(`[Bridge] ${data.toString().trim()}`);
|
||||
});
|
||||
}
|
||||
|
||||
bridgeProcess.on('error', (error) => {
|
||||
console.error('❌ Bridge process error:', error);
|
||||
});
|
||||
|
||||
bridgeProcess.on('exit', (code) => {
|
||||
console.error(`⚠️ Bridge process exited with code ${code}`);
|
||||
bridgeProcess = null;
|
||||
});
|
||||
|
||||
// Wait for the bridge to be ready
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
if (await isBridgeRunning()) {
|
||||
console.error('✅ Telegram bridge is ready');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Bridge failed to start after 5 seconds');
|
||||
}
|
||||
|
||||
// Ensure the bridge is running
|
||||
async function ensureBridge(): Promise<void> {
|
||||
if (await isBridgeRunning()) {
|
||||
console.error('✅ Telegram bridge is already running');
|
||||
return;
|
||||
}
|
||||
|
||||
await startBridge();
|
||||
}
|
||||
|
||||
// Define the Telegram bridge tools
|
||||
const TOOLS: Tool[] = [
|
||||
@@ -81,6 +166,14 @@ const TOOLS: Tool[] = [
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'telegram_toggle_afk',
|
||||
description: 'Toggle InnerVoice AFK mode - enables or disables Telegram notifications. Use this when going away from the system to enable notifications, or when back to disable them.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Create the MCP server
|
||||
@@ -235,6 +328,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
};
|
||||
}
|
||||
|
||||
case 'telegram_toggle_afk': {
|
||||
const response = await fetch(`${BRIDGE_URL}/toggle`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to toggle AFK mode');
|
||||
}
|
||||
|
||||
const result: any = await response.json();
|
||||
const icon = result.enabled ? '🟢' : '🔴';
|
||||
const status = result.enabled ? 'ENABLED' : 'DISABLED';
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `${icon} InnerVoice AFK mode toggled: ${status}\n\n${result.message}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
@@ -252,8 +369,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup function
|
||||
function cleanup() {
|
||||
console.error('\n👋 Shutting down MCP server...');
|
||||
if (bridgeProcess) {
|
||||
console.error('🛑 Stopping bridge process...');
|
||||
bridgeProcess.kill('SIGTERM');
|
||||
bridgeProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle shutdown signals
|
||||
process.on('SIGINT', () => {
|
||||
cleanup();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
cleanup();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Start the server
|
||||
async function main() {
|
||||
// Ensure the bridge is running before starting the MCP server
|
||||
await ensureBridge();
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error('🚀 Telegram MCP server running on stdio');
|
||||
@@ -261,5 +402,6 @@ async function main() {
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
cleanup();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user