Initial agent-news: Dart Telegram bot + Claude Code runner + MCP bridge

/news spawns claude-code as a local subprocess (single pod, no cross-container
exec on K3s); Claude delivers the digest itself via the send_to_telegram MCP tool.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 20:40:32 +03:00
commit be09de2ad7
15 changed files with 764 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
import 'dart:convert';
import 'dart:io';
import 'package:mcp_dart/mcp_dart.dart';
final _token = Platform.environment['TELEGRAM_BOT_TOKEN'] ?? '';
final _http = HttpClient();
CallToolResult _ok(String text) => CallToolResult(content: [TextContent(text: text)]);
CallToolResult _err(String text) =>
CallToolResult(content: [TextContent(text: 'Ошибка: $text')], isError: true);
List<String> _splitMessage(String text) {
const maxLen = 4000;
if (text.length <= maxLen) return [text];
final chunks = <String>[];
var start = 0;
while (start < text.length) {
final end = (start + maxLen).clamp(0, text.length);
chunks.add(text.substring(start, end));
start = end;
}
return chunks;
}
Future<void> _sendMessage(int chatId, String text) async {
for (final chunk in _splitMessage(text)) {
final req = await _http.postUrl(
Uri.parse('https://api.telegram.org/bot$_token/sendMessage'),
);
req.headers.contentType = ContentType.json;
req.write(jsonEncode({
'chat_id': chatId,
'text': chunk,
'parse_mode': 'Markdown',
}));
final res = await req.close();
final body = await res.transform(utf8.decoder).join();
if (res.statusCode >= 400) {
throw Exception('Telegram sendMessage → ${res.statusCode}: $body');
}
}
}
void main() {
final server = McpServer(
const Implementation(name: 'agent-news', version: '0.1.0'),
options: const McpServerOptions(
capabilities: ServerCapabilities(tools: ServerCapabilitiesTools()),
),
);
server.registerTool(
'send_to_telegram',
description:
'Отправляет готовый текст (Markdown) в указанный Telegram-чат. '
'Единственный способ доставить результат ресёрча пользователю — '
'вызывай этот тул с финальным оформленным текстом.',
inputSchema: JsonSchema.object(
properties: {
'chat_id': JsonSchema.integer(description: 'ID чата Telegram, куда отправить сообщение'),
'text': JsonSchema.string(description: 'Готовый текст в Markdown-разметке Telegram'),
},
required: ['chat_id', 'text'],
),
callback: (args, _) async {
if (_token.isEmpty) {
return _err('TELEGRAM_BOT_TOKEN не задан в окружении MCP-сервера');
}
try {
final chatId = args['chat_id'] as int;
final text = args['text'].toString();
await _sendMessage(chatId, text);
return _ok('Отправлено в чат $chatId (${text.length} символов).');
} catch (e) {
return _err('$e');
}
},
);
server.connect(StdioServerTransport());
}