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:
RichardDillman
2025-11-23 14:15:46 -05:00
parent c4cfcf6eb8
commit c45fe4d509
5 changed files with 338 additions and 15 deletions

5
.claude/commands/afk.md Normal file
View 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
View File

@@ -101,23 +101,63 @@ pnpm daemon
### 5. Add MCP Server to Claude ### 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 ```bash
cd innervoice # Navigate to the innervoice directory
./scripts/get-mcp-config.sh 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 ```json
{ {
"mcpServers": { "mcpServers": {
"innervoice": { "telegram": {
"command": "node", "command": "node",
"args": [ "args": [
"/ABSOLUTE/PATH/TO/innervoice/dist/mcp-server.js" "/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 ```bash
cd innervoice && pwd cd innervoice && pwd
# Use output: <result>/dist/mcp-server.js # 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` **Windows (Command Prompt):**
- **Per-project:** `your-project/.claude/mcp.json` ```cmd
- **VS Code:** `your-project/.vscode/mcp.json` cd innervoice
cd
# Use output: <result>\dist\mcp-server.js
```
### 6. Available Tools ### 6. Available Tools
@@ -150,6 +200,7 @@ Once configured, Claude can automatically use:
- `telegram_get_messages` - Check for messages from you - `telegram_get_messages` - Check for messages from you
- `telegram_reply` - Reply to your messages - `telegram_reply` - Reply to your messages
- `telegram_check_health` - Check bridge status - `telegram_check_health` - Check bridge status
- `telegram_toggle_afk` - Toggle AFK mode (enable/disable notifications)
**View detailed tool info:** **View detailed tool info:**
```bash ```bash
@@ -158,7 +209,60 @@ pnpm tools
node scripts/list-tools.js 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: 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." > "Let me verify the Telegram bridge is working."
> *Claude uses: `telegram_check_health({})`* > *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) ## Git Setup (For Sharing)
If you want to push this to your own Git repository: 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 ## Integration with ESO-MCP
Add this helper to your ESO-MCP project: Add this helper to your ESO-MCP project:

View File

@@ -3,7 +3,7 @@
"telegram": { "telegram": {
"command": "node", "command": "node",
"args": [ "args": [
"/ABSOLUTE/PATH/TO/claude-telegram-bridge/dist/mcp-server.js" "/Volumes/PRO-G40/Docker/innervoice/dist/mcp-server.js"
], ],
"env": { "env": {
"TELEGRAM_BRIDGE_URL": "http://localhost:3456" "TELEGRAM_BRIDGE_URL": "http://localhost:3456"

View File

@@ -10,7 +10,7 @@ const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN!);
const app = express(); const app = express();
const PORT = parseInt(process.env.PORT || '3456'); const PORT = parseInt(process.env.PORT || '3456');
const HOST = process.env.HOST || 'localhost'; 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; let chatId: string | null = process.env.TELEGRAM_CHAT_ID || null;
const envPath = path.join(process.cwd(), '.env'); 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 // Start bot
bot.launch().then(() => { bot.launch().then(() => {
console.log('🤖 Telegram bot started'); console.log('🤖 Telegram bot started');

View File

@@ -7,8 +7,93 @@ import {
ListToolsRequestSchema, ListToolsRequestSchema,
Tool, Tool,
} from '@modelcontextprotocol/sdk/types.js'; } 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_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 // Define the Telegram bridge tools
const TOOLS: Tool[] = [ const TOOLS: Tool[] = [
@@ -81,6 +166,14 @@ const TOOLS: Tool[] = [
properties: {}, 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 // 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: default:
throw new Error(`Unknown tool: ${name}`); 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 // Start the server
async function main() { async function main() {
// Ensure the bridge is running before starting the MCP server
await ensureBridge();
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();
await server.connect(transport); await server.connect(transport);
console.error('🚀 Telegram MCP server running on stdio'); console.error('🚀 Telegram MCP server running on stdio');
@@ -261,5 +402,6 @@ async function main() {
main().catch((error) => { main().catch((error) => {
console.error('Fatal error:', error); console.error('Fatal error:', error);
cleanup();
process.exit(1); process.exit(1);
}); });