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());
}

101
mcp_server/pubspec.lock Normal file
View File

@@ -0,0 +1,101 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
source: hosted
version: "2.13.1"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
mcp_dart:
dependency: "direct main"
description:
name: mcp_dart
sha256: "106118d1e7628becc72c8485dc1a3e2c8ed62a48527efc6d70b8576d3b18083f"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
meta:
dependency: transitive
description:
name: meta
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
url: "https://pub.dev"
source: hosted
version: "1.19.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
source_span:
dependency: transitive
description:
name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
url: "https://pub.dev"
source: hosted
version: "1.10.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
sdks:
dart: ">=3.11.0 <4.0.0"

10
mcp_server/pubspec.yaml Normal file
View File

@@ -0,0 +1,10 @@
name: agent_news_mcp
description: agent-news — MCP server exposing send_to_telegram for Claude Code
version: 0.1.0
publish_to: none
environment:
sdk: ^3.11.0
dependencies:
mcp_dart: ^1.2.0