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:
82
mcp_server/bin/agent_news_mcp.dart
Normal file
82
mcp_server/bin/agent_news_mcp.dart
Normal 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());
|
||||
}
|
||||
Reference in New Issue
Block a user