@ebowwa/glm-daemon-telegram 1.4.2 โ†’ 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebowwa/glm-daemon-telegram",
3
- "version": "1.4.2",
3
+ "version": "1.5.1",
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 || '';
@@ -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,9 @@ 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
503
+ // we are missing a few.. i.e. like /clean
470
504
  this.bot.onText(/\/start/, async (msg: TelegramBot.Message) => {
471
505
  const chatId = msg.chat.id;
472
506
  await this.bot.sendMessage(
@@ -722,19 +756,25 @@ class TelegramGLMBot {
722
756
  await this.bot.sendMessage(chatId, '๐Ÿงน Conversation memory cleared. Starting fresh!');
723
757
  });
724
758
 
725
- // /tools command - show available LLM tools
759
+ // /tools command - show available MCP and static tools
726
760
  this.bot.onText(/\/tools/, async (msg: TelegramBot.Message) => {
727
761
  const chatId = msg.chat.id;
728
- let toolsMsg = '*๐Ÿ› ๏ธ Available Tools*\n\n';
729
- toolsMsg += 'The AI can use these tools:\n\n';
762
+ let toolsMsg = `*๐Ÿ› ๏ธ Available Tools* (${mcpTools.length + TOOLS.length})\n\n`;
730
763
 
764
+ if (mcpTools.length > 0) {
765
+ toolsMsg += '*MCP Tools:*\n';
766
+ for (const tool of mcpTools) {
767
+ toolsMsg += `โ€ข \`${tool.name}\` [${tool.serverName}]\n`;
768
+ }
769
+ toolsMsg += '\n';
770
+ }
771
+
772
+ toolsMsg += '*Static Tools:*\n';
731
773
  for (const tool of TOOLS) {
732
- toolsMsg += `*${tool.name}*\n`;
733
- toolsMsg += `${tool.description}\n\n`;
774
+ toolsMsg += `โ€ข \`${tool.name}\`\n`;
734
775
  }
735
776
 
736
- toolsMsg += '_Just ask the AI to use them!_';
737
- toolsMsg += '\nExample: "Show me system info" or "List files in /root"';
777
+ toolsMsg += '\n_Just ask the AI to use them!_';
738
778
 
739
779
  await this.bot.sendMessage(chatId, toolsMsg, { parse_mode: 'Markdown' });
740
780
  });
@@ -814,7 +854,7 @@ class TelegramGLMBot {
814
854
  body: JSON.stringify({
815
855
  model: 'glm-4.7',
816
856
  messages,
817
- tools: GLM_TOOLS,
857
+ tools: getMCPTools(),
818
858
  temperature,
819
859
  max_tokens: maxTokens
820
860
  })
@@ -859,13 +899,18 @@ class TelegramGLMBot {
859
899
  const argsStr = Object.keys(args).length > 0 ? JSON.stringify(args) : '';
860
900
  await this.bot.sendMessage(chatId, `๐Ÿ”ง \`${toolName}\`${argsStr ? ' ' + argsStr : ''}`, { parse_mode: 'Markdown' });
861
901
 
862
- // Find and execute tool
863
- const tool = TOOLS.find(t => t.name === toolName);
902
+ // Execute tool - try MCP first, then static fallback
864
903
  let result: string;
865
- if (tool) {
866
- result = await tool.handler(args);
904
+ const mcpTool = mcpTools.find(t => t.name === toolName);
905
+ if (mcpClient && mcpTool) {
906
+ result = await mcpClient.callTool(toolName, args);
867
907
  } else {
868
- result = `Unknown tool: ${toolName}`;
908
+ const tool = TOOLS.find(t => t.name === toolName);
909
+ if (tool) {
910
+ result = await tool.handler(args);
911
+ } else {
912
+ result = `Unknown tool: ${toolName}`;
913
+ }
869
914
  }
870
915
 
871
916
  console.log(` โ† ${result.slice(0, 100)}...`);
@@ -947,6 +992,10 @@ async function main() {
947
992
  seedPrompts(promptsFile);
948
993
  }
949
994
 
995
+ // Initialize MCP client
996
+ console.log('๐Ÿ”Œ Initializing MCP client...');
997
+ await initMCP();
998
+
950
999
  console.log(`๐Ÿ“ฑ Bot token loaded: ${TELEGRAM_BOT_TOKEN.substring(0, 10)}...`);
951
1000
  console.log(`๐Ÿค– Bot name: @SimulationapiBot`);
952
1001
 
@@ -955,11 +1004,13 @@ async function main() {
955
1004
 
956
1005
  process.on('SIGINT', async () => {
957
1006
  await bot.stop();
1007
+ if (mcpClient) await mcpClient.disconnect();
958
1008
  process.exit(0);
959
1009
  });
960
1010
 
961
1011
  process.on('SIGTERM', async () => {
962
1012
  await bot.stop();
1013
+ if (mcpClient) await mcpClient.disconnect();
963
1014
  process.exit(0);
964
1015
  });
965
1016
  }
@@ -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: '/root/.bun/bin/bun',
131
+ args: ['x', '@ebowwa/hetzner-mcp'],
132
+ env: { HCLOUD_TOKEN: process.env.HCLOUD_TOKEN || '' }
133
+ },
134
+ {
135
+ name: 'tailscale',
136
+ command: '/root/.bun/bin/bun',
137
+ args: ['x', '@ebowwa/tailscale'],
138
+ },
139
+ {
140
+ name: 'tooling',
141
+ command: '/root/.bun/bin/bun',
142
+ args: ['x', '@ebowwa/tooling-mcp'],
143
+ },
144
+ {
145
+ name: 'terminal',
146
+ command: '/root/.bun/bin/bun',
147
+ args: ['x', '@ebowwa/terminal-mcp'],
148
+ },
149
+ {
150
+ name: 'github-search',
151
+ command: '/root/.bun/bin/bun',
152
+ args: ['x', '@ebowwa/github-search-mcp'],
153
+ env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN || '' }
154
+ }
155
+ ];