@ebowwa/glm-daemon-telegram 1.4.1 โ†’ 1.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebowwa/glm-daemon-telegram",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Telegram adapter for GLM Daemon with ButlerAgent personality",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,6 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@ebowwa/structured-prompts": "0.2.4",
47
+ "@modelcontextprotocol/sdk": "^1.0.4",
47
48
  "node-telegram-bot-api": "^0.66.0"
48
49
  },
49
50
  "devDependencies": {
package/src/index.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * GLM Daemon Telegram Bot
3
3
  *
4
- * Full-featured Telegram bot with GLM-4.7 AI, git status, Doppler integration
4
+ * Full-featured Telegram bot with GLM-4.7 AI and MCP tool integration
5
5
  *
6
6
  * Features:
7
7
  * - GLM-4.7 AI responses via Z.AI API
8
+ * - Dynamic MCP tool discovery and execution
8
9
  * - 429 rate limit handling with exponential backoff
9
10
  * - /git command for git status + GitHub auth
10
11
  * - /doppler command for Doppler secrets status
@@ -15,6 +16,7 @@ import TelegramBot from 'node-telegram-bot-api';
15
16
  import { execSync } from 'child_process';
16
17
  import { getStore, seedPrompts } from '@ebowwa/structured-prompts';
17
18
  import { readFileSync, writeFileSync, existsSync } from 'fs';
19
+ import { MCPClient, DEFAULT_MCP_SERVERS, MCPTool } from './mcp-client.js';
18
20
 
19
21
  const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || '';
20
22
  const TELEGRAM_TEST_CHAT_ID = process.env.TELEGRAM_TEST_CHAT_ID || '';
@@ -272,7 +274,7 @@ const TOOLS: Tool[] = [
272
274
  parameters: { type: 'object', properties: {} },
273
275
  handler: async () => {
274
276
  try {
275
- const result = execSync('doppler secrets --plain 2>&1 | cut -f1').toString();
277
+ const result = execSync('doppler secrets 2>&1').toString();
276
278
  return result;
277
279
  } catch (e: any) {
278
280
  if (e.message?.includes('not found')) return 'doppler CLI not installed';
@@ -286,7 +288,7 @@ const TOOLS: Tool[] = [
286
288
  parameters: { type: 'object', properties: {} },
287
289
  handler: async () => {
288
290
  try {
289
- const result = execSync('doppler projects list 2>&1').toString();
291
+ const result = execSync('doppler projects 2>&1').toString();
290
292
  return result;
291
293
  } catch (e: any) {
292
294
  if (e.message?.includes('not found')) return 'doppler CLI not installed';
@@ -306,7 +308,7 @@ const TOOLS: Tool[] = [
306
308
  handler: async (args) => {
307
309
  try {
308
310
  const project = args.project ? `-p ${args.project}` : '';
309
- const result = execSync(`doppler configs list ${project} 2>&1`).toString();
311
+ const result = execSync(`doppler configs ${project} 2>&1`).toString();
310
312
  return result;
311
313
  } catch (e: any) {
312
314
  return e.stdout?.toString() || e.message;
@@ -383,15 +385,44 @@ const TOOLS: Tool[] = [
383
385
  }
384
386
  ];
385
387
 
388
+ // ====================================================================
389
+ // MCP Client - Dynamic tool discovery from MCP servers
390
+ // ====================================================================
391
+
392
+ let mcpClient: MCPClient | null = null;
393
+ let mcpTools: MCPTool[] = [];
394
+
395
+ async function initMCP(): Promise<void> {
396
+ mcpClient = new MCPClient(DEFAULT_MCP_SERVERS);
397
+ await mcpClient.connect();
398
+ mcpTools = mcpClient.getTools();
399
+ console.log(`๐Ÿ“ฆ Loaded ${mcpTools.length} MCP tools`);
400
+ }
401
+
386
402
  // Convert tools to GLM API format
387
- const GLM_TOOLS = TOOLS.map(t => ({
388
- type: 'function',
389
- function: {
390
- name: t.name,
391
- description: t.description,
392
- parameters: t.parameters
393
- }
394
- }));
403
+ function getGLMTools() {
404
+ // MCP tools
405
+ const mcpGLMTools = mcpTools.map(t => ({
406
+ type: 'function',
407
+ function: {
408
+ name: t.name,
409
+ description: t.description,
410
+ parameters: t.inputSchema
411
+ }
412
+ }));
413
+
414
+ // Static tools as fallback
415
+ const staticGLMTools = TOOLS.map(t => ({
416
+ type: 'function',
417
+ function: {
418
+ name: t.name,
419
+ description: t.description,
420
+ parameters: t.parameters
421
+ }
422
+ }));
423
+
424
+ return [...mcpGLMTools, ...staticGLMTools];
425
+ }
395
426
 
396
427
  // API Key Resolution - supports rolling keys
397
428
  const getZAIKey = (): string | null => {
@@ -467,6 +498,8 @@ class TelegramGLMBot {
467
498
  console.log('๐Ÿค– GLM Daemon Telegram Bot starting with GLM-4.7...');
468
499
 
469
500
  // /start command
501
+ //
502
+ // TODO: files to the telegram commands
470
503
  this.bot.onText(/\/start/, async (msg: TelegramBot.Message) => {
471
504
  const chatId = msg.chat.id;
472
505
  await this.bot.sendMessage(
@@ -722,19 +755,25 @@ class TelegramGLMBot {
722
755
  await this.bot.sendMessage(chatId, '๐Ÿงน Conversation memory cleared. Starting fresh!');
723
756
  });
724
757
 
725
- // /tools command - show available LLM tools
758
+ // /tools command - show available MCP and static tools
726
759
  this.bot.onText(/\/tools/, async (msg: TelegramBot.Message) => {
727
760
  const chatId = msg.chat.id;
728
- let toolsMsg = '*๐Ÿ› ๏ธ Available Tools*\n\n';
729
- toolsMsg += 'The AI can use these tools:\n\n';
761
+ let toolsMsg = `*๐Ÿ› ๏ธ Available Tools* (${mcpTools.length + TOOLS.length})\n\n`;
730
762
 
763
+ if (mcpTools.length > 0) {
764
+ toolsMsg += '*MCP Tools:*\n';
765
+ for (const tool of mcpTools) {
766
+ toolsMsg += `โ€ข \`${tool.name}\` [${tool.serverName}]\n`;
767
+ }
768
+ toolsMsg += '\n';
769
+ }
770
+
771
+ toolsMsg += '*Static Tools:*\n';
731
772
  for (const tool of TOOLS) {
732
- toolsMsg += `*${tool.name}*\n`;
733
- toolsMsg += `${tool.description}\n\n`;
773
+ toolsMsg += `โ€ข \`${tool.name}\`\n`;
734
774
  }
735
775
 
736
- toolsMsg += '_Just ask the AI to use them!_';
737
- toolsMsg += '\nExample: "Show me system info" or "List files in /root"';
776
+ toolsMsg += '\n_Just ask the AI to use them!_';
738
777
 
739
778
  await this.bot.sendMessage(chatId, toolsMsg, { parse_mode: 'Markdown' });
740
779
  });
@@ -814,7 +853,7 @@ class TelegramGLMBot {
814
853
  body: JSON.stringify({
815
854
  model: 'glm-4.7',
816
855
  messages,
817
- tools: GLM_TOOLS,
856
+ tools: getMCPTools(),
818
857
  temperature,
819
858
  max_tokens: maxTokens
820
859
  })
@@ -859,13 +898,18 @@ class TelegramGLMBot {
859
898
  const argsStr = Object.keys(args).length > 0 ? JSON.stringify(args) : '';
860
899
  await this.bot.sendMessage(chatId, `๐Ÿ”ง \`${toolName}\`${argsStr ? ' ' + argsStr : ''}`, { parse_mode: 'Markdown' });
861
900
 
862
- // Find and execute tool
863
- const tool = TOOLS.find(t => t.name === toolName);
901
+ // Execute tool - try MCP first, then static fallback
864
902
  let result: string;
865
- if (tool) {
866
- result = await tool.handler(args);
903
+ const mcpTool = mcpTools.find(t => t.name === toolName);
904
+ if (mcpClient && mcpTool) {
905
+ result = await mcpClient.callTool(toolName, args);
867
906
  } else {
868
- result = `Unknown tool: ${toolName}`;
907
+ const tool = TOOLS.find(t => t.name === toolName);
908
+ if (tool) {
909
+ result = await tool.handler(args);
910
+ } else {
911
+ result = `Unknown tool: ${toolName}`;
912
+ }
869
913
  }
870
914
 
871
915
  console.log(` โ† ${result.slice(0, 100)}...`);
@@ -947,6 +991,10 @@ async function main() {
947
991
  seedPrompts(promptsFile);
948
992
  }
949
993
 
994
+ // Initialize MCP client
995
+ console.log('๐Ÿ”Œ Initializing MCP client...');
996
+ await initMCP();
997
+
950
998
  console.log(`๐Ÿ“ฑ Bot token loaded: ${TELEGRAM_BOT_TOKEN.substring(0, 10)}...`);
951
999
  console.log(`๐Ÿค– Bot name: @SimulationapiBot`);
952
1000
 
@@ -955,11 +1003,13 @@ async function main() {
955
1003
 
956
1004
  process.on('SIGINT', async () => {
957
1005
  await bot.stop();
1006
+ if (mcpClient) await mcpClient.disconnect();
958
1007
  process.exit(0);
959
1008
  });
960
1009
 
961
1010
  process.on('SIGTERM', async () => {
962
1011
  await bot.stop();
1012
+ if (mcpClient) await mcpClient.disconnect();
963
1013
  process.exit(0);
964
1014
  });
965
1015
  }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * MCP Client for glm-daemon-telegram
3
+ *
4
+ * Connects to MCP servers via stdio and discovers their tools dynamically.
5
+ */
6
+
7
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
8
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
9
+
10
+ export interface MCPTool {
11
+ name: string;
12
+ description: string;
13
+ inputSchema: Record<string, unknown>;
14
+ serverName: string;
15
+ }
16
+
17
+ export interface MCPServerConfig {
18
+ name: string;
19
+ command: string;
20
+ args?: string[];
21
+ env?: Record<string, string>;
22
+ }
23
+
24
+ export class MCPClient {
25
+ private clients: Map<string, Client> = new Map();
26
+ private transports: Map<string, StdioClientTransport> = new Map();
27
+ private tools: MCPTool[] = [];
28
+ private servers: MCPServerConfig[];
29
+
30
+ constructor(servers: MCPServerConfig[]) {
31
+ this.servers = servers;
32
+ }
33
+
34
+ async connect(): Promise<void> {
35
+ for (const server of this.servers) {
36
+ try {
37
+ const transport = new StdioClientTransport({
38
+ command: server.command,
39
+ args: server.args || [],
40
+ env: { ...process.env, ...server.env }
41
+ });
42
+
43
+ const client = new Client({ name: 'glm-daemon-telegram', version: '1.0.0' }, {
44
+ capabilities: { tools: {} }
45
+ });
46
+
47
+ await client.connect(transport);
48
+
49
+ this.clients.set(server.name, client);
50
+ this.transports.set(server.name, transport);
51
+
52
+ // Discover tools
53
+ const toolsResult = await client.listTools();
54
+ for (const tool of toolsResult.tools) {
55
+ this.tools.push({
56
+ name: tool.name,
57
+ description: tool.description || '',
58
+ inputSchema: tool.inputSchema as Record<string, unknown>,
59
+ serverName: server.name
60
+ });
61
+ }
62
+
63
+ console.log(`โœ“ MCP connected: ${server.name} (${toolsResult.tools.length} tools)`);
64
+ } catch (e) {
65
+ console.error(`โœ— MCP failed: ${server.name} - ${(e as Error).message}`);
66
+ }
67
+ }
68
+ }
69
+
70
+ getTools(): MCPTool[] {
71
+ return this.tools;
72
+ }
73
+
74
+ getToolsForGLM(): Array<{ type: string; function: { name: string; description: string; parameters: Record<string, unknown> } }> {
75
+ return this.tools.map(t => ({
76
+ type: 'function',
77
+ function: {
78
+ name: t.name,
79
+ description: `[${t.serverName}] ${t.description}`,
80
+ parameters: t.inputSchema
81
+ }
82
+ }));
83
+ }
84
+
85
+ async callTool(name: string, args: Record<string, unknown>): Promise<string> {
86
+ const tool = this.tools.find(t => t.name === name);
87
+ if (!tool) {
88
+ return `Unknown tool: ${name}`;
89
+ }
90
+
91
+ const client = this.clients.get(tool.serverName);
92
+ if (!client) {
93
+ return `MCP server not connected: ${tool.serverName}`;
94
+ }
95
+
96
+ try {
97
+ const result = await client.callTool({ name, arguments: args });
98
+
99
+ if (result.content && Array.isArray(result.content)) {
100
+ return result.content
101
+ .map((c: any) => c.text || JSON.stringify(c))
102
+ .join('\n');
103
+ }
104
+
105
+ return JSON.stringify(result);
106
+ } catch (e) {
107
+ return `Tool error: ${(e as Error).message}`;
108
+ }
109
+ }
110
+
111
+ async disconnect(): Promise<void> {
112
+ for (const [name, client] of this.clients) {
113
+ try {
114
+ await client.close();
115
+ console.log(`โœ“ MCP disconnected: ${name}`);
116
+ } catch (e) {
117
+ console.error(`โœ— MCP disconnect error: ${name} - ${(e as Error).message}`);
118
+ }
119
+ }
120
+ this.clients.clear();
121
+ this.transports.clear();
122
+ this.tools = [];
123
+ }
124
+ }
125
+
126
+ // Default MCP servers configuration for VPS node
127
+ export const DEFAULT_MCP_SERVERS: MCPServerConfig[] = [
128
+ {
129
+ name: 'hetzner',
130
+ command: 'bunx',
131
+ args: ['@ebowwa/hetzner-mcp'],
132
+ env: { HCLOUD_TOKEN: process.env.HCLOUD_TOKEN || '' }
133
+ },
134
+ {
135
+ name: 'tailscale',
136
+ command: 'bunx',
137
+ args: ['@ebowwa/tailscale'],
138
+ },
139
+ {
140
+ name: 'tooling',
141
+ command: 'bunx',
142
+ args: ['@ebowwa/tooling-mcp'],
143
+ },
144
+ {
145
+ name: 'terminal',
146
+ command: 'bunx',
147
+ args: ['@ebowwa/terminal-mcp'],
148
+ },
149
+ {
150
+ name: 'github-search',
151
+ command: 'bunx',
152
+ args: ['@ebowwa/github-search-mcp'],
153
+ env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN || '' }
154
+ }
155
+ ];