@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 +2 -1
- package/src/index.ts +73 -22
- 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.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
|
|
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
|
-
|
|
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,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
|
|
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 =
|
|
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 +=
|
|
733
|
-
toolsMsg += `${tool.description}\n\n`;
|
|
774
|
+
toolsMsg += `โข \`${tool.name}\`\n`;
|
|
734
775
|
}
|
|
735
776
|
|
|
736
|
-
toolsMsg += '
|
|
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:
|
|
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
|
-
//
|
|
863
|
-
const tool = TOOLS.find(t => t.name === toolName);
|
|
902
|
+
// Execute tool - try MCP first, then static fallback
|
|
864
903
|
let result: string;
|
|
865
|
-
|
|
866
|
-
|
|
904
|
+
const mcpTool = mcpTools.find(t => t.name === toolName);
|
|
905
|
+
if (mcpClient && mcpTool) {
|
|
906
|
+
result = await mcpClient.callTool(toolName, args);
|
|
867
907
|
} else {
|
|
868
|
-
|
|
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
|
+
];
|