telegram-claude-mcp 1.6.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,234 @@
1
+ # Architecture Options for Multi-Session MCP
2
+
3
+ This document describes three approaches for handling multiple Claude Code sessions with a shared Telegram bot.
4
+
5
+ ## Current Architecture (Baseline)
6
+
7
+ ```
8
+ ┌─────────────────┐ stdio ┌─────────────────────────────────────┐
9
+ │ Claude Code 1 │◄──────────────►│ MCP Server 1 (port 3333) │
10
+ │ │ │ ├─ TelegramManager (polling) │──►┌──────────┐
11
+ │ │ │ └─ HTTP Hook Server │ │ │
12
+ └─────────────────┘ └─────────────────────────────────────┘ │ │
13
+ │ Telegram │
14
+ ┌─────────────────┐ stdio ┌─────────────────────────────────────┐ │ Bot │
15
+ │ Claude Code 2 │◄──────────────►│ MCP Server 2 (port 3334) │ │ │
16
+ │ │ │ ├─ TelegramManager (polling) │──►│ │
17
+ │ │ │ └─ HTTP Hook Server │ └──────────┘
18
+ └─────────────────┘ └─────────────────────────────────────┘
19
+
20
+ Hook scripts discover ports via /tmp/telegram-claude-sessions/*.info files
21
+ ```
22
+
23
+ **Problems:**
24
+ - Multiple bot polling instances (potential rate limiting)
25
+ - Port collision handling complexity
26
+ - Session discovery via filesystem
27
+ - Each process manages its own state
28
+
29
+ ---
30
+
31
+ ## Option 1: Proxy Pattern (Recommended)
32
+
33
+ A singleton daemon handles all Telegram communication. Thin MCP proxies forward requests.
34
+
35
+ ```
36
+ ┌─────────────────┐ stdio ┌─────────────────┐ Unix Socket ┌─────────────────────────────┐
37
+ │ Claude Code 1 │◄──────────────►│ MCP Proxy 1 │◄───────────────────►│ │
38
+ │ │ │ (session: proj1)│ │ Singleton Daemon │
39
+ └─────────────────┘ └─────────────────┘ │ (single process) │
40
+ │ │
41
+ ┌─────────────────┐ stdio ┌─────────────────┐ Unix Socket │ ├─ TelegramManager │──►┌──────────┐
42
+ │ Claude Code 2 │◄──────────────►│ MCP Proxy 2 │◄───────────────────►│ │ (single polling) │ │ Telegram │
43
+ │ │ │ (session: proj2)│ │ ├─ SessionManager │ │ Bot │
44
+ └─────────────────┘ └─────────────────┘ │ │ (tracks all sessions) │ └──────────┘
45
+ │ └─ HTTP Hook Server │
46
+ ┌─────────────────┐ HTTP POST │ (single port: 3333) │
47
+ │ Hook Scripts │────────────────────►│ │
48
+ └─────────────────┘ └─────────────────────────────┘
49
+ ```
50
+
51
+ ### Components
52
+
53
+ **1. Singleton Daemon (`telegram-claude-daemon`)**
54
+ - Single long-running process
55
+ - Manages one TelegramManager with single bot polling
56
+ - Exposes Unix socket for proxy connections
57
+ - Exposes single HTTP port for all hooks
58
+ - Tracks all active sessions in memory
59
+ - Routes messages to correct session
60
+
61
+ **2. MCP Proxy (`telegram-claude-proxy`)**
62
+ - Spawned by Claude Code (stdio transport)
63
+ - Connects to daemon via Unix socket
64
+ - Registers session with unique ID
65
+ - Forwards MCP tool calls to daemon
66
+ - Receives responses from daemon
67
+
68
+ ### Protocol (Daemon <-> Proxy)
69
+
70
+ ```typescript
71
+ // Proxy -> Daemon
72
+ interface ProxyMessage {
73
+ type: 'connect' | 'disconnect' | 'tool_call';
74
+ sessionId: string;
75
+ sessionName: string;
76
+ payload?: {
77
+ tool: string;
78
+ arguments: Record<string, unknown>;
79
+ };
80
+ }
81
+
82
+ // Daemon -> Proxy
83
+ interface DaemonMessage {
84
+ type: 'connected' | 'tool_result' | 'error';
85
+ sessionId: string;
86
+ payload?: {
87
+ result?: unknown;
88
+ error?: string;
89
+ };
90
+ }
91
+ ```
92
+
93
+ ### Advantages
94
+ - Single bot polling (no rate limiting)
95
+ - Single port for hooks (no discovery needed)
96
+ - Centralized session management
97
+ - Clean separation of concerns
98
+ - Daemon can outlive individual sessions
99
+
100
+ ### Disadvantages
101
+ - Requires daemon to be running before Claude Code starts
102
+ - Additional IPC complexity
103
+ - Single point of failure
104
+
105
+ ### Implementation Files
106
+ ```
107
+ server/src/
108
+ ├── daemon/
109
+ │ ├── index.ts # Daemon entry point
110
+ │ ├── session-manager.ts # Track active sessions
111
+ │ └── ipc-server.ts # Unix socket server
112
+ ├── proxy/
113
+ │ ├── index.ts # Proxy entry point (MCP stdio)
114
+ │ └── ipc-client.ts # Unix socket client
115
+ └── shared/
116
+ └── protocol.ts # Shared types and constants
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Option 2: Shared State Pattern
122
+
123
+ Keep MCP-per-session but share Telegram connection via IPC to a daemon.
124
+
125
+ ```
126
+ ┌─────────────────┐ stdio ┌─────────────────────────────────────┐
127
+ │ Claude Code 1 │◄──────────────►│ MCP Server 1 │
128
+ │ │ │ ├─ Full MCP implementation │
129
+ │ │ │ ├─ HTTP Hook Server (port 3333) │───┐
130
+ └─────────────────┘ │ └─ IPC Client │ │
131
+ └─────────────────────────────────────┘ │
132
+ │ │
133
+ │ IPC (send/receive) │ HTTP
134
+ ▼ │
135
+ ┌─────────────────────────────────────┐ │
136
+ │ Telegram Daemon │ │
137
+ │ ├─ TelegramManager (single poll) │──►┌──────────┐
138
+ │ └─ Message Router │ │ Telegram │
139
+ └─────────────────────────────────────┘ │ Bot │
140
+ ▲ └──────────┘
141
+ │ IPC (send/receive)
142
+
143
+ ┌─────────────────┐ stdio ┌─────────────────────────────────────┐
144
+ │ Claude Code 2 │◄──────────────►│ MCP Server 2 │
145
+ │ │ │ ├─ Full MCP implementation │
146
+ │ │ │ ├─ HTTP Hook Server (port 3334) │
147
+ │ │ │ └─ IPC Client │
148
+ └─────────────────┘ └─────────────────────────────────────┘
149
+ ```
150
+
151
+ ### How It Works
152
+ 1. Each MCP server maintains its own HTTP hook server
153
+ 2. Telegram operations are forwarded to a shared daemon
154
+ 3. Daemon handles actual Telegram API calls
155
+ 4. Responses routed back via IPC
156
+
157
+ ### Advantages
158
+ - MCP servers remain mostly unchanged
159
+ - Gradual migration path
160
+ - Each session still has its own hook endpoint
161
+
162
+ ### Disadvantages
163
+ - Still requires port management for hooks
164
+ - More complex message routing
165
+ - Partial duplication of logic
166
+
167
+ ---
168
+
169
+ ## Option 3: Session ID in Tool Calls
170
+
171
+ Single MCP server process, sessions identified by explicit IDs in every tool call.
172
+
173
+ ```
174
+ ┌─────────────────┐ ┌─────────────────────────────┐
175
+ │ Claude Code 1 │──┐ │ │
176
+ │ │ │ Multiple stdio │ Singleton MCP Server │
177
+ └─────────────────┘ │ connections via │ │
178
+ │ wrapper/multiplexer │ ├─ TelegramManager │──►┌──────────┐
179
+ ┌─────────────────┐ │ │ ├─ SessionManager │ │ Telegram │
180
+ │ Claude Code 2 │──┼──────────────────────────►│ └─ HTTP Hook Server │ │ Bot │
181
+ │ │ │ │ │ └──────────┘
182
+ └─────────────────┘ │ │ Tools include session_id: │
183
+ │ │ - send_message(session_id) │
184
+ ┌─────────────────┐ │ │ - continue_chat(session_id)│
185
+ │ Claude Code 3 │──┘ │ │
186
+ └─────────────────┘ └─────────────────────────────┘
187
+ ```
188
+
189
+ ### How It Works
190
+ 1. Single MCP server handles all sessions
191
+ 2. `connect` tool registers new session, returns session_id
192
+ 3. All other tools require session_id parameter
193
+ 4. Server routes operations by session_id
194
+
195
+ ### Challenges
196
+ - MCP's stdio transport is 1:1 (one client per process)
197
+ - Would need a multiplexer/wrapper to share one MCP server
198
+ - Violates MCP design assumptions
199
+ - Complex error handling when sessions disconnect
200
+
201
+ ### Advantages
202
+ - Simplest daemon architecture
203
+ - Single process for everything
204
+
205
+ ### Disadvantages
206
+ - Requires custom MCP multiplexer
207
+ - Not standard MCP usage
208
+ - Harder to implement correctly
209
+
210
+ ---
211
+
212
+ ## Comparison Matrix
213
+
214
+ | Aspect | Current | Option 1 (Proxy) | Option 2 (Shared) | Option 3 (Session ID) |
215
+ |---------------------------|--------------|-------------------|-------------------|----------------------|
216
+ | Bot polling instances | N | 1 | 1 | 1 |
217
+ | Port management | Complex | Simple (1 port) | Complex | Simple (1 port) |
218
+ | MCP compatibility | Standard | Standard | Standard | Non-standard |
219
+ | Implementation effort | Done | Medium | Medium-High | High |
220
+ | Session isolation | Full | Full | Full | Logical only |
221
+ | Daemon dependency | No | Yes | Yes | Yes |
222
+ | Hook simplicity | Medium | Simple | Medium | Simple |
223
+
224
+ ---
225
+
226
+ ## Recommended: Option 1 (Proxy Pattern)
227
+
228
+ Option 1 provides the best balance of:
229
+ - **Simplicity**: Single port, single polling instance
230
+ - **Compatibility**: Standard MCP stdio transport
231
+ - **Reliability**: Clean session management
232
+ - **Maintainability**: Clear separation between proxy and daemon
233
+
234
+ The proxy is lightweight and only handles MCP protocol translation, while the daemon manages all Telegram complexity.
package/README.md CHANGED
@@ -35,29 +35,29 @@ Create directory `~/.claude/hooks/` and add these scripts:
35
35
  **~/.claude/hooks/permission-hook.sh**
36
36
  ```bash
37
37
  #!/bin/bash
38
+ # Set SESSION_NAME env var to target a specific session (for multi-session setups)
38
39
  SESSION_DIR="/tmp/telegram-claude-sessions"
39
40
 
40
- find_active_session() {
41
- local latest_file=""
42
- local latest_time=0
41
+ find_session() {
42
+ if [ -n "$SESSION_NAME" ]; then
43
+ local f="$SESSION_DIR/${SESSION_NAME}.info"
44
+ [ -f "$f" ] && { local p=$(jq -r '.pid // empty' "$f" 2>/dev/null); [ -n "$p" ] && kill -0 "$p" 2>/dev/null && echo "$f"; return; }
45
+ return
46
+ fi
47
+ local latest="" latest_time=0
43
48
  [ -d "$SESSION_DIR" ] || return
44
- for info_file in "$SESSION_DIR"/*.info; do
45
- [ -e "$info_file" ] || continue
46
- local pid
47
- pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
48
- if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
49
- local file_time
50
- file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
51
- if [ "$file_time" -gt "$latest_time" ]; then
52
- latest_time=$file_time
53
- latest_file=$info_file
54
- fi
55
- fi
49
+ for f in "$SESSION_DIR"/*.info; do
50
+ [ -e "$f" ] || continue
51
+ local p=$(jq -r '.pid // empty' "$f" 2>/dev/null)
52
+ [ -n "$p" ] && kill -0 "$p" 2>/dev/null && {
53
+ local t=$(stat -f %m "$f" 2>/dev/null || stat -c %Y "$f" 2>/dev/null)
54
+ [ "$t" -gt "$latest_time" ] && { latest_time=$t; latest=$f; }
55
+ }
56
56
  done
57
- echo "$latest_file"
57
+ echo "$latest"
58
58
  }
59
59
 
60
- INFO_FILE=$(find_active_session)
60
+ INFO_FILE=$(find_session)
61
61
  [ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0
62
62
 
63
63
  HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
@@ -78,33 +78,33 @@ RESPONSE=$(curl -s -X POST "$HOOK_URL" -H "Content-Type: application/json" -d "$
78
78
  **~/.claude/hooks/stop-hook.sh**
79
79
  ```bash
80
80
  #!/bin/bash
81
+ # Set SESSION_NAME env var to target a specific session (for multi-session setups)
81
82
  SESSION_DIR="/tmp/telegram-claude-sessions"
82
83
 
83
- find_active_session() {
84
- local latest_file=""
85
- local latest_time=0
84
+ find_session() {
85
+ if [ -n "$SESSION_NAME" ]; then
86
+ local f="$SESSION_DIR/${SESSION_NAME}.info"
87
+ [ -f "$f" ] && { local p=$(jq -r '.pid // empty' "$f" 2>/dev/null); [ -n "$p" ] && kill -0 "$p" 2>/dev/null && echo "$f"; return; }
88
+ return
89
+ fi
90
+ local latest="" latest_time=0
86
91
  [ -d "$SESSION_DIR" ] || return
87
- for info_file in "$SESSION_DIR"/*.info; do
88
- [ -e "$info_file" ] || continue
89
- local pid
90
- pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
91
- if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
92
- local file_time
93
- file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
94
- if [ "$file_time" -gt "$latest_time" ]; then
95
- latest_time=$file_time
96
- latest_file=$info_file
97
- fi
98
- fi
92
+ for f in "$SESSION_DIR"/*.info; do
93
+ [ -e "$f" ] || continue
94
+ local p=$(jq -r '.pid // empty' "$f" 2>/dev/null)
95
+ [ -n "$p" ] && kill -0 "$p" 2>/dev/null && {
96
+ local t=$(stat -f %m "$f" 2>/dev/null || stat -c %Y "$f" 2>/dev/null)
97
+ [ "$t" -gt "$latest_time" ] && { latest_time=$t; latest=$f; }
98
+ }
99
99
  done
100
- echo "$latest_file"
100
+ echo "$latest"
101
101
  }
102
102
 
103
103
  INPUT=$(cat)
104
104
  STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
105
105
  [ "$STOP_HOOK_ACTIVE" = "true" ] && exit 0
106
106
 
107
- INFO_FILE=$(find_active_session)
107
+ INFO_FILE=$(find_session)
108
108
  [ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0
109
109
 
110
110
  HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
@@ -124,29 +124,29 @@ fi
124
124
  **~/.claude/hooks/notify-hook.sh**
125
125
  ```bash
126
126
  #!/bin/bash
127
+ # Set SESSION_NAME env var to target a specific session (for multi-session setups)
127
128
  SESSION_DIR="/tmp/telegram-claude-sessions"
128
129
 
129
- find_active_session() {
130
- local latest_file=""
131
- local latest_time=0
130
+ find_session() {
131
+ if [ -n "$SESSION_NAME" ]; then
132
+ local f="$SESSION_DIR/${SESSION_NAME}.info"
133
+ [ -f "$f" ] && { local p=$(jq -r '.pid // empty' "$f" 2>/dev/null); [ -n "$p" ] && kill -0 "$p" 2>/dev/null && echo "$f"; return; }
134
+ return
135
+ fi
136
+ local latest="" latest_time=0
132
137
  [ -d "$SESSION_DIR" ] || return
133
- for info_file in "$SESSION_DIR"/*.info; do
134
- [ -e "$info_file" ] || continue
135
- local pid
136
- pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
137
- if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
138
- local file_time
139
- file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
140
- if [ "$file_time" -gt "$latest_time" ]; then
141
- latest_time=$file_time
142
- latest_file=$info_file
143
- fi
144
- fi
138
+ for f in "$SESSION_DIR"/*.info; do
139
+ [ -e "$f" ] || continue
140
+ local p=$(jq -r '.pid // empty' "$f" 2>/dev/null)
141
+ [ -n "$p" ] && kill -0 "$p" 2>/dev/null && {
142
+ local t=$(stat -f %m "$f" 2>/dev/null || stat -c %Y "$f" 2>/dev/null)
143
+ [ "$t" -gt "$latest_time" ] && { latest_time=$t; latest=$f; }
144
+ }
145
145
  done
146
- echo "$latest_file"
146
+ echo "$latest"
147
147
  }
148
148
 
149
- INFO_FILE=$(find_active_session)
149
+ INFO_FILE=$(find_session)
150
150
  [ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0
151
151
 
152
152
  HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
@@ -175,19 +175,19 @@ Add to `~/.claude/settings.json`:
175
175
  "PermissionRequest": [
176
176
  {
177
177
  "matcher": "*",
178
- "hooks": [{ "type": "command", "command": "~/.claude/hooks/permission-hook.sh" }]
178
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=default ~/.claude/hooks/permission-hook.sh" }]
179
179
  }
180
180
  ],
181
181
  "Stop": [
182
182
  {
183
183
  "matcher": "*",
184
- "hooks": [{ "type": "command", "command": "~/.claude/hooks/stop-hook.sh" }]
184
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=default ~/.claude/hooks/stop-hook.sh" }]
185
185
  }
186
186
  ],
187
187
  "Notification": [
188
188
  {
189
189
  "matcher": "*",
190
- "hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-hook.sh" }]
190
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=default ~/.claude/hooks/notify-hook.sh" }]
191
191
  }
192
192
  ]
193
193
  },
@@ -205,6 +205,8 @@ Add to `~/.claude/settings.json`:
205
205
  }
206
206
  ```
207
207
 
208
+ **Important:** The `SESSION_NAME` in hook commands must match the `SESSION_NAME` in the MCP server env.
209
+
208
210
  ### 3. Create Telegram Bot
209
211
 
210
212
  1. Open Telegram and message [@BotFather](https://t.me/BotFather)
@@ -257,17 +259,84 @@ Claude Code Hook Scripts telegram-claude-mcp
257
259
 
258
260
  ## Multiple Sessions
259
261
 
260
- Run multiple Claude instances with different session names:
262
+ Run multiple Claude Code instances (e.g., `~/.claude` and `~/.claude-personal`) with proper message routing.
263
+
264
+ ### Why This Matters
265
+
266
+ 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.
267
+
268
+ ### Configuration
269
+
270
+ Each Claude config needs a unique `SESSION_NAME` in **both** the MCP server env and the hook commands.
271
+
272
+ **~/.claude/settings.json** (main):
273
+ ```json
274
+ {
275
+ "hooks": {
276
+ "PermissionRequest": [{
277
+ "matcher": "*",
278
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks/permission-hook.sh" }]
279
+ }],
280
+ "Stop": [{
281
+ "matcher": "*",
282
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks/stop-hook.sh" }]
283
+ }],
284
+ "Notification": [{
285
+ "matcher": "*",
286
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks/notify-hook.sh" }]
287
+ }]
288
+ },
289
+ "mcpServers": {
290
+ "telegram": {
291
+ "command": "npx",
292
+ "args": ["-y", "telegram-claude-mcp"],
293
+ "env": {
294
+ "TELEGRAM_BOT_TOKEN": "YOUR_BOT_TOKEN",
295
+ "TELEGRAM_CHAT_ID": "YOUR_CHAT_ID",
296
+ "SESSION_NAME": "main"
297
+ }
298
+ }
299
+ }
300
+ }
301
+ ```
261
302
 
303
+ **~/.claude-personal/settings.json** (personal):
262
304
  ```json
263
305
  {
264
- "env": {
265
- "SESSION_NAME": "project-a"
306
+ "hooks": {
307
+ "PermissionRequest": [{
308
+ "matcher": "*",
309
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=personal ~/.claude/hooks/permission-hook.sh" }]
310
+ }],
311
+ "Stop": [{
312
+ "matcher": "*",
313
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=personal ~/.claude/hooks/stop-hook.sh" }]
314
+ }],
315
+ "Notification": [{
316
+ "matcher": "*",
317
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=personal ~/.claude/hooks/notify-hook.sh" }]
318
+ }]
319
+ },
320
+ "mcpServers": {
321
+ "telegram": {
322
+ "command": "npx",
323
+ "args": ["-y", "telegram-claude-mcp"],
324
+ "env": {
325
+ "TELEGRAM_BOT_TOKEN": "YOUR_BOT_TOKEN",
326
+ "TELEGRAM_CHAT_ID": "YOUR_CHAT_ID",
327
+ "SESSION_NAME": "personal"
328
+ }
329
+ }
266
330
  }
267
331
  }
268
332
  ```
269
333
 
270
- Messages are tagged with `[project-a]` so you know which instance sent them.
334
+ ### Key Points
335
+
336
+ - **SESSION_NAME must match** - The env var in hook commands must match the MCP server's SESSION_NAME
337
+ - **Share hook scripts** - All configs can use the same hook scripts in `~/.claude/hooks/`
338
+ - **Message tagging** - Messages are tagged with `[main]` or `[personal]` so you know the source
339
+ - **Separate ports** - Each MCP server auto-discovers an available port
271
340
 
272
341
  ## Troubleshooting
273
342
 
@@ -284,6 +353,128 @@ Messages are tagged with `[project-a]` so you know which instance sent them.
284
353
  - Verify hook scripts are executable: `ls -la ~/.claude/hooks/`
285
354
  - Check Claude settings have hooks configured
286
355
 
356
+ ---
357
+
358
+ ## v2: Daemon Architecture (Recommended for Multiple Sessions)
359
+
360
+ Version 2 introduces a singleton daemon architecture that handles all Telegram communication from one process. This solves:
361
+ - Multiple bot polling instances causing rate limiting
362
+ - Complex port discovery between sessions
363
+ - Session state synchronization issues
364
+
365
+ See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed design documentation.
366
+
367
+ ### Quick Start with Daemon
368
+
369
+ **Option A: Auto-start (recommended)**
370
+
371
+ The proxy can auto-start the daemon if you provide credentials in the MCP config:
372
+
373
+ ```json
374
+ {
375
+ "mcpServers": {
376
+ "telegram": {
377
+ "command": "npx",
378
+ "args": ["-y", "telegram-claude-proxy"],
379
+ "env": {
380
+ "SESSION_NAME": "main",
381
+ "TELEGRAM_BOT_TOKEN": "YOUR_BOT_TOKEN",
382
+ "TELEGRAM_CHAT_ID": "YOUR_CHAT_ID"
383
+ }
384
+ }
385
+ }
386
+ }
387
+ ```
388
+
389
+ The first Claude session will start the daemon automatically. Subsequent sessions connect to the already-running daemon.
390
+
391
+ **Option B: Manual daemon start**
392
+
393
+ Start the daemon once, then proxies connect without needing credentials:
394
+
395
+ ```bash
396
+ # Start daemon with credentials
397
+ TELEGRAM_BOT_TOKEN=xxx TELEGRAM_CHAT_ID=yyy telegram-claude-ctl start
398
+
399
+ # Check status
400
+ telegram-claude-ctl status
401
+ ```
402
+
403
+ 2. **Configure Claude to use the proxy** in `~/.claude/settings.json`:
404
+ ```json
405
+ {
406
+ "hooks": {
407
+ "PermissionRequest": [{
408
+ "matcher": "*",
409
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks-v2/permission-hook.sh" }]
410
+ }],
411
+ "Stop": [{
412
+ "matcher": "*",
413
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks-v2/stop-hook.sh" }]
414
+ }],
415
+ "Notification": [{
416
+ "matcher": "*",
417
+ "hooks": [{ "type": "command", "command": "SESSION_NAME=main ~/.claude/hooks-v2/notify-hook.sh" }]
418
+ }]
419
+ },
420
+ "mcpServers": {
421
+ "telegram": {
422
+ "command": "npx",
423
+ "args": ["-y", "telegram-claude-proxy"],
424
+ "env": {
425
+ "SESSION_NAME": "main"
426
+ }
427
+ }
428
+ }
429
+ }
430
+ ```
431
+
432
+ ### Daemon Commands
433
+
434
+ ```bash
435
+ # Start daemon in background
436
+ telegram-claude-ctl start
437
+
438
+ # Stop daemon
439
+ telegram-claude-ctl stop
440
+
441
+ # Restart daemon
442
+ telegram-claude-ctl restart
443
+
444
+ # Check status (shows active sessions)
445
+ telegram-claude-ctl status
446
+ ```
447
+
448
+ ### Architecture Overview
449
+
450
+ ```
451
+ ┌─────────────────┐ stdio ┌─────────────────┐ Unix Socket ┌─────────────────────┐
452
+ │ Claude Code 1 │◄──────────────►│ MCP Proxy 1 │◄───────────────────►│ │
453
+ └─────────────────┘ └─────────────────┘ │ Singleton Daemon │
454
+ │ │
455
+ ┌─────────────────┐ stdio ┌─────────────────┐ Unix Socket │ - Single bot poll │──► Telegram
456
+ │ Claude Code 2 │◄──────────────►│ MCP Proxy 2 │◄───────────────────►│ - Session manager │
457
+ └─────────────────┘ └─────────────────┘ │ - HTTP hooks :3333 │
458
+ └─────────────────────┘
459
+ ```
460
+
461
+ ### Environment Variables (Daemon)
462
+
463
+ | Variable | Description | Default |
464
+ |----------|-------------|---------|
465
+ | `TELEGRAM_BOT_TOKEN` | Bot token from @BotFather | Required |
466
+ | `TELEGRAM_CHAT_ID` | Your Telegram chat ID | Required |
467
+ | `CHAT_RESPONSE_TIMEOUT_MS` | Response timeout | 600000 (10 min) |
468
+ | `PERMISSION_TIMEOUT_MS` | Permission timeout | 600000 (10 min) |
469
+
470
+ ### Environment Variables (Proxy/Hooks)
471
+
472
+ | Variable | Description | Default |
473
+ |----------|-------------|---------|
474
+ | `SESSION_NAME` | Session identifier | Auto-detected from CWD |
475
+ | `TELEGRAM_CLAUDE_PORT` | Daemon HTTP port | 3333 |
476
+ | `TELEGRAM_CLAUDE_HOST` | Daemon host | localhost |
477
+
287
478
  ## License
288
479
 
289
480
  MIT