From c45fe4d5096861b3308e3ec458d15a9501a72c76 Mon Sep 17 00:00:00 2001 From: RichardDillman Date: Sun, 23 Nov 2025 14:15:46 -0500 Subject: [PATCH] feat: add auto-start, AFK mode, and comprehensive cross-platform setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .claude/commands/afk.md | 5 ++ README.md | 168 ++++++++++++++++++++++++++++++++++++---- mcp-config.json | 2 +- src/index.ts | 36 ++++++++- src/mcp-server.ts | 142 +++++++++++++++++++++++++++++++++ 5 files changed, 338 insertions(+), 15 deletions(-) create mode 100644 .claude/commands/afk.md diff --git a/.claude/commands/afk.md b/.claude/commands/afk.md new file mode 100644 index 0000000..b69bc66 --- /dev/null +++ b/.claude/commands/afk.md @@ -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. diff --git a/README.md b/README.md index d42cfcf..b1e6099 100644 --- a/README.md +++ b/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: /dist/mcp-server.js ``` -#### MCP Config Locations +**Windows (PowerShell):** +```powershell +cd innervoice +(Get-Location).Path +# Use output: \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: \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: diff --git a/mcp-config.json b/mcp-config.json index c19edb2..5e77e23 100644 --- a/mcp-config.json +++ b/mcp-config.json @@ -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" diff --git a/src/index.ts b/src/index.ts index eb70565..c672105 100644 --- a/src/index.ts +++ b/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'); diff --git a/src/mcp-server.ts b/src/mcp-server.ts index db14b1e..39fce8a 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -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 { + 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 { + 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 { + 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); });