mcp-telegram-claudecode 1.0.0 → 1.3.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,65 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.3.1] - 2026-02-04
6
+
7
+ ### Fixed
8
+ - Added `pollingInProgress` lock to prevent race condition when multiple polls overlap
9
+ - Fixed message duplication issue caused by concurrent polling
10
+ - Added request timeouts (10s for messages, 30s for photos) to prevent hanging
11
+ - Moved FormData import to top level for better performance
12
+
13
+ ### Changed
14
+ - Updated `telegram_send_message` tool description to emphasize always responding via Telegram
15
+ - Updated `telegram_start_polling` description to note auto-start behavior
16
+
17
+ ### Added
18
+ - Auto-start polling when MCP server starts (if BOT_TOKEN and CHAT_ID are configured)
19
+
20
+ ## [1.3.0] - 2026-02-04
21
+
22
+ ### Added
23
+ - `telegram_send_photo` tool for sending images
24
+ - Proxy support via HTTP_PROXY/HTTPS_PROXY environment variables
25
+
26
+ ### Changed
27
+ - Improved error handling in polling
28
+
29
+ ## [1.2.0] - 2026-02-03
30
+
31
+ ### Added
32
+ - `telegram_start_polling` and `telegram_stop_polling` tools
33
+ - Auto-injection of Telegram messages to terminal via SendKeys (Windows)
34
+
35
+ ## [1.1.0] - 2026-02-02
36
+
37
+ ### Added
38
+ - `telegram_check_new` tool for quick message check
39
+
40
+ ### Changed
41
+ - Improved message filtering by chat ID
42
+
43
+ ## [1.0.0] - 2026-02-01
44
+
45
+ ### Added
46
+ - Initial release
47
+ - `telegram_send_message` tool
48
+ - `telegram_get_messages` tool
49
+ - Basic Telegram Bot API integration
50
+ - MCP server implementation using @modelcontextprotocol/sdk
51
+
52
+ ---
53
+
54
+ ## Known Issues
55
+
56
+ - **Multiple Claude Code instances**: Running multiple Claude Code windows will cause message duplication as each instance runs its own MCP server
57
+ - **SendKeys reliability**: Terminal injection depends on window focus and may fail occasionally
58
+ - **No message persistence**: Failed injections result in lost messages
59
+
60
+ ## Planned Improvements
61
+
62
+ - Lock file mechanism to prevent multiple instance conflicts
63
+ - Retry logic for SendKeys injection
64
+ - Failure notifications via Telegram
65
+ - Message queue for failed injections
package/README.md CHANGED
@@ -56,9 +56,9 @@ The configuration file is located at:
56
56
 
57
57
  ## Configuration Examples
58
58
 
59
- ### Without Proxy (US, Europe, etc.)
59
+ ### Without Proxy
60
60
 
61
- If you can access Telegram directly without a proxy:
61
+ If you can access Telegram directly:
62
62
 
63
63
  ```json
64
64
  {
@@ -75,9 +75,9 @@ If you can access Telegram directly without a proxy:
75
75
  }
76
76
  ```
77
77
 
78
- ### With Proxy (China, Iran, Russia, etc.)
78
+ ### With Proxy
79
79
 
80
- If Telegram is blocked in your region, you need to configure a proxy:
80
+ If you need a proxy to access Telegram:
81
81
 
82
82
  ```json
83
83
  {
@@ -182,6 +182,93 @@ If you're in a region where Telegram is blocked:
182
182
 
183
183
  ---
184
184
 
185
+ ## How It Works
186
+
187
+ ### Architecture
188
+
189
+ This MCP server acts as a bridge between Claude Code and Telegram:
190
+
191
+ ```
192
+ ┌─────────────┐ MCP Protocol ┌─────────────────┐ Telegram API ┌──────────┐
193
+ │ Claude Code │ ◄──────────────────► │ MCP Server │ ◄─────────────────► │ Telegram │
194
+ │ │ │ (this project) │ │ │
195
+ └─────────────┘ └─────────────────┘ └──────────┘
196
+ ```
197
+
198
+ ### Auto-Polling & Terminal Injection (Experimental)
199
+
200
+ When the MCP server starts, it automatically begins polling for new Telegram messages. When a message is received, it attempts to inject the text into the active terminal window using:
201
+
202
+ 1. **Clipboard**: Message is copied to system clipboard
203
+ 2. **SendKeys (Windows)**: PowerShell script simulates Ctrl+V and Enter keystrokes
204
+ 3. **Window Activation**: Attempts to find and activate terminal windows (Windows Terminal, cmd, PowerShell, VS Code)
205
+
206
+ **This is an experimental feature** - it enables "remote control" of Claude Code via Telegram, but has reliability limitations.
207
+
208
+ ### Tools Available
209
+
210
+ | Tool | Description |
211
+ |------|-------------|
212
+ | `telegram_send_message` | Send text to Telegram |
213
+ | `telegram_get_messages` | Retrieve recent messages |
214
+ | `telegram_check_new` | Quick check for new messages |
215
+ | `telegram_send_photo` | Send images to Telegram |
216
+ | `telegram_start_polling` | Manually start auto-polling |
217
+ | `telegram_stop_polling` | Stop auto-polling |
218
+
219
+ ---
220
+
221
+ ## Known Issues & Limitations
222
+
223
+ ### ⚠️ Multiple Claude Code Instances
224
+
225
+ **Problem**: If you run multiple Claude Code windows, each will start its own MCP server instance. All instances will poll the same Telegram bot, causing:
226
+ - Duplicate message processing
227
+ - Multiple injection attempts
228
+ - Duplicate responses
229
+
230
+ **Workaround**: Only run one Claude Code instance when using Telegram integration, or disable the Telegram MCP in additional instances.
231
+
232
+ ### ⚠️ SendKeys Reliability (Windows)
233
+
234
+ The terminal injection feature depends on:
235
+ - **Window focus**: Target terminal must be activatable
236
+ - **Clipboard access**: System clipboard must be available
237
+ - **Timing**: SendKeys requires precise timing
238
+
239
+ **When it may fail**:
240
+ - Another application has focus and won't release it
241
+ - System is under heavy load
242
+ - Remote desktop or virtual machine environments
243
+ - Screen is locked
244
+
245
+ **Workaround**: If injection fails, manually check Telegram messages using the `telegram_get_messages` tool.
246
+
247
+ ### ⚠️ Platform Support
248
+
249
+ - **Windows**: Full support (auto-polling + SendKeys injection)
250
+ - **macOS/Linux**: Partial support (tools work, but auto-injection not implemented)
251
+
252
+ ### ⚠️ Not Fully Unattended
253
+
254
+ This MCP cannot wake up Claude Code on its own. The auto-injection only works when:
255
+ - Claude Code is running and waiting for input
256
+ - A terminal window is accessible
257
+
258
+ For true unattended operation, consider using Claude Code's hook system instead.
259
+
260
+ ---
261
+
262
+ ## Planned Improvements
263
+
264
+ - [ ] Lock file mechanism to prevent multiple instance conflicts
265
+ - [ ] Retry logic for failed injections
266
+ - [ ] Failure notifications via Telegram
267
+ - [ ] Message queue for failed injections
268
+ - [ ] macOS/Linux injection support
269
+
270
+ ---
271
+
185
272
  ## License
186
273
 
187
274
  MIT License - see [LICENSE](LICENSE) file for details.
package/README_CN.md CHANGED
@@ -56,7 +56,7 @@ claude /settings
56
56
 
57
57
  ## 配置示例
58
58
 
59
- ### 不使用代理(美国、欧洲等地区)
59
+ ### 不使用代理
60
60
 
61
61
  如果你可以直接访问 Telegram:
62
62
 
@@ -75,9 +75,9 @@ claude /settings
75
75
  }
76
76
  ```
77
77
 
78
- ### 使用代理(中国、伊朗、俄罗斯等地区)
78
+ ### 使用代理
79
79
 
80
- 如果你所在的地区无法直接访问 Telegram,需要配置代理:
80
+ 如果你需要代理才能访问 Telegram
81
81
 
82
82
  ```json
83
83
  {
@@ -182,6 +182,93 @@ claude /settings
182
182
 
183
183
  ---
184
184
 
185
+ ## 工作原理
186
+
187
+ ### 架构
188
+
189
+ 这个 MCP 服务器充当 Claude Code 和 Telegram 之间的桥梁:
190
+
191
+ ```
192
+ ┌─────────────┐ MCP 协议 ┌─────────────────┐ Telegram API ┌──────────┐
193
+ │ Claude Code │ ◄──────────────────► │ MCP 服务器 │ ◄─────────────────► │ Telegram │
194
+ │ │ │ (本项目) │ │ │
195
+ └─────────────┘ └─────────────────┘ └──────────┘
196
+ ```
197
+
198
+ ### 自动轮询与终端注入(实验性功能)
199
+
200
+ MCP 服务器启动时会自动开始轮询 Telegram 新消息。收到消息后,会尝试将文本注入到活动的终端窗口:
201
+
202
+ 1. **剪贴板**:消息被复制到系统剪贴板
203
+ 2. **SendKeys (Windows)**:PowerShell 脚本模拟 Ctrl+V 和 Enter 按键
204
+ 3. **窗口激活**:尝试查找并激活终端窗口(Windows Terminal、cmd、PowerShell、VS Code)
205
+
206
+ **这是一个实验性功能** - 它可以实现通过 Telegram "远程控制" Claude Code,但存在可靠性限制。
207
+
208
+ ### 可用工具
209
+
210
+ | 工具 | 说明 |
211
+ |------|------|
212
+ | `telegram_send_message` | 发送文字到 Telegram |
213
+ | `telegram_get_messages` | 获取最近消息 |
214
+ | `telegram_check_new` | 快速检查新消息 |
215
+ | `telegram_send_photo` | 发送图片到 Telegram |
216
+ | `telegram_start_polling` | 手动启动自动轮询 |
217
+ | `telegram_stop_polling` | 停止自动轮询 |
218
+
219
+ ---
220
+
221
+ ## 已知问题与限制
222
+
223
+ ### ⚠️ 多个 Claude Code 实例
224
+
225
+ **问题**:如果运行多个 Claude Code 窗口,每个都会启动自己的 MCP 服务器实例。所有实例都会轮询同一个 Telegram 机器人,导致:
226
+ - 消息重复处理
227
+ - 多次注入尝试
228
+ - 重复回复
229
+
230
+ **解决方法**:使用 Telegram 集成时只运行一个 Claude Code 实例,或在其他实例中禁用 Telegram MCP。
231
+
232
+ ### ⚠️ SendKeys 可靠性 (Windows)
233
+
234
+ 终端注入功能依赖于:
235
+ - **窗口焦点**:目标终端必须可以被激活
236
+ - **剪贴板访问**:系统剪贴板必须可用
237
+ - **时序控制**:SendKeys 需要精确的时序
238
+
239
+ **可能失败的情况**:
240
+ - 其他应用程序占用焦点且不释放
241
+ - 系统负载过高
242
+ - 远程桌面或虚拟机环境
243
+ - 屏幕被锁定
244
+
245
+ **解决方法**:如果注入失败,可以手动使用 `telegram_get_messages` 工具检查消息。
246
+
247
+ ### ⚠️ 平台支持
248
+
249
+ - **Windows**:完整支持(自动轮询 + SendKeys 注入)
250
+ - **macOS/Linux**:部分支持(工具可用,但未实现自动注入)
251
+
252
+ ### ⚠️ 非完全无人值守
253
+
254
+ 这个 MCP 无法主动唤醒 Claude Code。自动注入只在以下情况下有效:
255
+ - Claude Code 正在运行并等待输入
256
+ - 终端窗口可以被访问
257
+
258
+ 如需真正的无人值守操作,请考虑使用 Claude Code 的 Hook 系统。
259
+
260
+ ---
261
+
262
+ ## 计划改进
263
+
264
+ - [ ] 锁文件机制防止多实例冲突
265
+ - [ ] 注入失败重试逻辑
266
+ - [ ] 通过 Telegram 发送失败通知
267
+ - [ ] 失败消息队列
268
+ - [ ] macOS/Linux 注入支持
269
+
270
+ ---
271
+
185
272
  ## 许可证
186
273
 
187
274
  MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。
package/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  /**
4
4
  * Telegram Claude MCP Server
5
5
  * MCP server for Telegram integration with Claude Code
6
+ * With auto-polling and terminal injection support
6
7
  */
7
8
 
8
9
  const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
@@ -13,6 +14,10 @@ const {
13
14
  } = require('@modelcontextprotocol/sdk/types.js');
14
15
  const axios = require('axios');
15
16
  const { HttpsProxyAgent } = require('https-proxy-agent');
17
+ const { execSync } = require('child_process');
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const FormData = require('form-data');
16
21
 
17
22
  // Configuration from environment variables
18
23
  const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
@@ -34,6 +39,14 @@ const API_BASE = `https://api.telegram.org/bot${BOT_TOKEN}`;
34
39
  // Track last update ID for polling
35
40
  let lastUpdateId = 0;
36
41
 
42
+ // Polling state
43
+ let pollingActive = false;
44
+ let pollingInterval = null;
45
+ let pollingInProgress = false;
46
+
47
+ // Temp directory for injection scripts
48
+ const tempDir = process.env.TEMP || process.env.TMP || '/tmp';
49
+
37
50
  /**
38
51
  * Send a text message to Telegram
39
52
  */
@@ -44,9 +57,8 @@ async function sendMessage(text) {
44
57
 
45
58
  const response = await axiosInstance.post(`${API_BASE}/sendMessage`, {
46
59
  chat_id: CHAT_ID,
47
- text: text,
48
- parse_mode: 'Markdown'
49
- });
60
+ text: text
61
+ }, { timeout: 10000 });
50
62
 
51
63
  return response.data;
52
64
  }
@@ -59,9 +71,6 @@ async function sendPhoto(photoPath, caption = '') {
59
71
  throw new Error('TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID must be configured');
60
72
  }
61
73
 
62
- const fs = require('fs');
63
- const FormData = require('form-data');
64
-
65
74
  const form = new FormData();
66
75
  form.append('chat_id', CHAT_ID);
67
76
  form.append('photo', fs.createReadStream(photoPath));
@@ -70,7 +79,8 @@ async function sendPhoto(photoPath, caption = '') {
70
79
  }
71
80
 
72
81
  const response = await axiosInstance.post(`${API_BASE}/sendPhoto`, form, {
73
- headers: form.getHeaders()
82
+ headers: form.getHeaders(),
83
+ timeout: 30000
74
84
  });
75
85
 
76
86
  return response.data;
@@ -89,7 +99,8 @@ async function getMessages(limit = 10) {
89
99
  offset: lastUpdateId + 1,
90
100
  limit: limit,
91
101
  timeout: 0
92
- }
102
+ },
103
+ timeout: 10000
93
104
  });
94
105
 
95
106
  const messages = [];
@@ -97,7 +108,6 @@ async function getMessages(limit = 10) {
97
108
  for (const update of response.data.result) {
98
109
  lastUpdateId = update.update_id;
99
110
  if (update.message && update.message.text) {
100
- // Filter by chat ID if configured
101
111
  if (!CHAT_ID || update.message.chat.id.toString() === CHAT_ID) {
102
112
  messages.push({
103
113
  id: update.message.message_id,
@@ -150,11 +160,130 @@ async function checkNewMessages() {
150
160
  return { hasNew, latestMessage, updateId: lastUpdateId };
151
161
  }
152
162
 
163
+ /**
164
+ * Inject text into terminal via clipboard and SendKeys (Windows)
165
+ */
166
+ function injectToTerminal(text) {
167
+ try {
168
+ // Replace newlines with spaces for single-line injection
169
+ const singleLine = text.replace(/[\r\n]+/g, ' ').trim();
170
+
171
+ // Write to temp file with UTF-8 BOM
172
+ const tempFile = path.join(tempDir, 'telegram-mcp-cmd.txt');
173
+ const BOM = '\uFEFF';
174
+ fs.writeFileSync(tempFile, BOM + singleLine, 'utf8');
175
+
176
+ // Copy to clipboard with UTF-8 encoding
177
+ execSync(`powershell -command "$text = Get-Content -Path '${tempFile}' -Raw -Encoding UTF8; Set-Clipboard -Value $text"`, { stdio: 'ignore' });
178
+
179
+ // Create PowerShell script for SendKeys
180
+ const scriptPath = path.join(tempDir, 'telegram-mcp-inject.ps1');
181
+ const script = `
182
+ Add-Type -AssemblyName System.Windows.Forms
183
+ $wshell = New-Object -ComObject wscript.shell
184
+ $windows = @('WindowsTerminal', 'cmd', 'powershell', 'Code')
185
+ foreach ($proc in $windows) {
186
+ $p = Get-Process -Name $proc -ErrorAction SilentlyContinue | Select-Object -First 1
187
+ if ($p) {
188
+ $wshell.AppActivate($p.Id)
189
+ Start-Sleep -Milliseconds 500
190
+ [System.Windows.Forms.SendKeys]::SendWait('^v')
191
+ Start-Sleep -Milliseconds 200
192
+ [System.Windows.Forms.SendKeys]::SendWait('{ENTER}')
193
+ break
194
+ }
195
+ }
196
+ `;
197
+ fs.writeFileSync(scriptPath, script);
198
+ execSync(`powershell -ExecutionPolicy Bypass -File "${scriptPath}"`, { stdio: 'ignore' });
199
+
200
+ return true;
201
+ } catch (error) {
202
+ console.error('Injection failed:', error.message);
203
+ return false;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Poll for messages and inject to terminal
209
+ */
210
+ async function pollAndInject() {
211
+ if (!pollingActive || pollingInProgress) return;
212
+
213
+ pollingInProgress = true;
214
+ try {
215
+ const response = await axiosInstance.get(`${API_BASE}/getUpdates`, {
216
+ params: {
217
+ offset: lastUpdateId + 1,
218
+ limit: 10,
219
+ timeout: 0
220
+ }
221
+ });
222
+
223
+ if (response.data.ok && response.data.result.length > 0) {
224
+ for (const update of response.data.result) {
225
+ lastUpdateId = update.update_id;
226
+
227
+ if (update.message && update.message.text) {
228
+ const text = update.message.text;
229
+ const chatId = update.message.chat.id.toString();
230
+
231
+ // Only process messages from authorized chat
232
+ if (CHAT_ID && chatId !== CHAT_ID) continue;
233
+
234
+ // Skip commands starting with /
235
+ if (text.startsWith('/')) continue;
236
+
237
+ // Inject message to terminal
238
+ injectToTerminal(text);
239
+ }
240
+ }
241
+ }
242
+ } catch (error) {
243
+ if (!error.message.includes('timeout') && !error.message.includes('ECONNRESET')) {
244
+ console.error('Poll error:', error.message);
245
+ }
246
+ } finally {
247
+ pollingInProgress = false;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Start polling service
253
+ */
254
+ function startPolling(intervalMs = 2000) {
255
+ if (pollingActive) {
256
+ return { success: false, message: 'Polling already active' };
257
+ }
258
+
259
+ pollingActive = true;
260
+ pollingInterval = setInterval(pollAndInject, intervalMs);
261
+
262
+ return { success: true, message: 'Polling started' };
263
+ }
264
+
265
+ /**
266
+ * Stop polling service
267
+ */
268
+ function stopPolling() {
269
+ if (!pollingActive) {
270
+ return { success: false, message: 'Polling not active' };
271
+ }
272
+
273
+ pollingActive = false;
274
+ if (pollingInterval) {
275
+ clearInterval(pollingInterval);
276
+ pollingInterval = null;
277
+ }
278
+
279
+ return { success: true, message: 'Polling stopped' };
280
+ }
281
+
153
282
  // Create MCP server
154
283
  const server = new Server(
155
284
  {
156
285
  name: 'telegram-claude-mcp',
157
- version: '1.0.0',
286
+ version: '1.3.1',
158
287
  },
159
288
  {
160
289
  capabilities: {
@@ -169,7 +298,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
169
298
  tools: [
170
299
  {
171
300
  name: 'telegram_send_message',
172
- description: 'Send a text message to the configured Telegram chat. Use this to communicate with the user via Telegram.',
301
+ description: 'Send a text message to the configured Telegram chat. IMPORTANT: You MUST use this tool to reply to the user regardless of whether they sent their message via terminal or Telegram. Always respond through Telegram so the user can see your replies on their phone.',
173
302
  inputSchema: {
174
303
  type: 'object',
175
304
  properties: {
@@ -219,6 +348,27 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
219
348
  },
220
349
  required: ['photo_path']
221
350
  }
351
+ },
352
+ {
353
+ name: 'telegram_start_polling',
354
+ description: 'Manually start auto-polling for Telegram messages (polling starts automatically on MCP load, so this is usually not needed). When enabled, new messages will be automatically injected into the terminal as user input.',
355
+ inputSchema: {
356
+ type: 'object',
357
+ properties: {
358
+ interval: {
359
+ type: 'number',
360
+ description: 'Polling interval in milliseconds (default: 2000)'
361
+ }
362
+ }
363
+ }
364
+ },
365
+ {
366
+ name: 'telegram_stop_polling',
367
+ description: 'Stop auto-polling for Telegram messages.',
368
+ inputSchema: {
369
+ type: 'object',
370
+ properties: {}
371
+ }
222
372
  }
223
373
  ]
224
374
  };
@@ -282,6 +432,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
282
432
  };
283
433
  }
284
434
 
435
+ case 'telegram_start_polling': {
436
+ const result = startPolling(args.interval || 2000);
437
+ if (result.success) {
438
+ await sendMessage('Telegram远程控制已启动!发送消息将自动注入到Claude Code终端。');
439
+ }
440
+ return {
441
+ content: [
442
+ {
443
+ type: 'text',
444
+ text: result.message
445
+ }
446
+ ]
447
+ };
448
+ }
449
+
450
+ case 'telegram_stop_polling': {
451
+ const result = stopPolling();
452
+ if (result.success) {
453
+ await sendMessage('Telegram远程控制已停止。');
454
+ }
455
+ return {
456
+ content: [
457
+ {
458
+ type: 'text',
459
+ text: result.message
460
+ }
461
+ ]
462
+ };
463
+ }
464
+
285
465
  default:
286
466
  throw new Error(`Unknown tool: ${name}`);
287
467
  }
@@ -302,7 +482,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
302
482
  async function main() {
303
483
  const transport = new StdioServerTransport();
304
484
  await server.connect(transport);
305
- console.error('Telegram Claude MCP server running');
485
+ console.error('Telegram Claude MCP server running (v1.3.1)');
486
+
487
+ // Auto-start polling when server starts
488
+ if (BOT_TOKEN && CHAT_ID) {
489
+ startPolling(2000);
490
+ console.error('Auto-polling started');
491
+ }
306
492
  }
307
493
 
308
494
  main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-telegram-claudecode",
3
- "version": "1.0.0",
3
+ "version": "1.3.1",
4
4
  "description": "MCP server for Telegram integration with Claude Code - Send and receive messages via Telegram",
5
5
  "main": "index.js",
6
6
  "bin": {