@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 +2 -1
- package/src/index.ts +75 -25
- package/src/mcp-client.ts +155 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ebowwa/glm-daemon-telegram",
|
|
3
|
-
"version": "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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
|
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 =
|
|
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 +=
|
|
733
|
-
toolsMsg += `${tool.description}\n\n`;
|
|
773
|
+
toolsMsg += `โข \`${tool.name}\`\n`;
|
|
734
774
|
}
|
|
735
775
|
|
|
736
|
-
toolsMsg += '
|
|
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:
|
|
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
|
-
//
|
|
863
|
-
const tool = TOOLS.find(t => t.name === toolName);
|
|
901
|
+
// Execute tool - try MCP first, then static fallback
|
|
864
902
|
let result: string;
|
|
865
|
-
|
|
866
|
-
|
|
903
|
+
const mcpTool = mcpTools.find(t => t.name === toolName);
|
|
904
|
+
if (mcpClient && mcpTool) {
|
|
905
|
+
result = await mcpClient.callTool(toolName, args);
|
|
867
906
|
} else {
|
|
868
|
-
|
|
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
|
+
];
|