NNTP MCP-сервер: мониторинг новостных групп и ответы в тредах.
Сценарий: скрипт запускается периодически, scan_group находит новые сообщения с момента прошлого запуска, возвращает треды целиком (с контекстом), LLM принимает решение, post_reply отправляет ответ. Состояние хранится в nntp_mcp_state.json рядом со скриптом.
This commit is contained in:
1 parent
8b4659cffb
commit
367f024cbd
4 files changed
+486
No files matched your search
@@ -427,6 +427,72 @@ def get_latest_messages(group_name: str, count: int = 10) -> list[dict]:
|
||||
except Exception as e:
|
||||
return [{"error": f"Ошибка NNTP при получении последних сообщений: {e}"}]
|
||||
|
||||
@mcp.tool()
|
||||
def get_thread_root(message_id: str) -> dict:
|
||||
"""
|
||||
Находит Message-ID первого (корневого) сообщения в ветке обсуждения.
|
||||
|
||||
:param message_id: Message-ID любого сообщения из ветки (например, '<abc@example.com>')
|
||||
:return: Словарь с полями root_message_id, subject, author или error
|
||||
"""
|
||||
try:
|
||||
with nntplib.NNTP(NNTP_SERVER, port=PORT, timeout=10) as s:
|
||||
# Получаем исходное сообщение
|
||||
resp, info = s.article(message_id)
|
||||
raw_bytes = b"\n".join(info.lines)
|
||||
msg = email.message_from_bytes(raw_bytes, policy=default)
|
||||
|
||||
# Проверяем заголовок References - там все Message-ID в цепочке
|
||||
references = msg.get("References", "").strip()
|
||||
|
||||
if references:
|
||||
# References содержит список ID через пробел, первый - корень ветки
|
||||
ref_ids = re.findall(r'<[^>]+>', references)
|
||||
if ref_ids:
|
||||
root_id = ref_ids[0]
|
||||
|
||||
# Получаем информацию о корневом сообщении
|
||||
try:
|
||||
resp_root, info_root = s.article(root_id)
|
||||
raw_root = b"\n".join(info_root.lines)
|
||||
root_msg = email.message_from_bytes(raw_root, policy=default)
|
||||
|
||||
return {
|
||||
"root_message_id": root_id,
|
||||
"subject": decode_mime_header(root_msg.get("Subject", "[Без темы]")),
|
||||
"author": decode_mime_header(root_msg.get("From", "[Неизвестно]")),
|
||||
"date": root_msg.get("Date", "")
|
||||
}
|
||||
except Exception:
|
||||
# Если корневое сообщение недоступно, возвращаем хотя бы его ID
|
||||
return {
|
||||
"root_message_id": root_id,
|
||||
"subject": "[Сообщение недоступно]",
|
||||
"author": "[Неизвестно]",
|
||||
"date": ""
|
||||
}
|
||||
|
||||
# Если References нет, проверяем In-Reply-To и поднимаемся рекурсивно
|
||||
in_reply_to = msg.get("In-Reply-To", "").strip()
|
||||
|
||||
if in_reply_to:
|
||||
# Извлекаем Message-ID из In-Reply-To
|
||||
match = re.search(r'<[^>]+>', in_reply_to)
|
||||
if match:
|
||||
parent_id = match.group(0)
|
||||
# Рекурсивно ищем корень через родительское сообщение
|
||||
return get_thread_root(parent_id)
|
||||
|
||||
# Если ни References, ни In-Reply-To нет - это и есть корень
|
||||
return {
|
||||
"root_message_id": message_id,
|
||||
"subject": decode_mime_header(msg.get("Subject", "[Без темы]")),
|
||||
"author": decode_mime_header(msg.get("From", "[Неизвестно]")),
|
||||
"date": msg.get("Date", "")
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {"error": f"Не удалось найти корень ветки для {message_id}: {e}"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user