ellmos-servercommander-mcp 0.1.0-alpha.3 → 0.1.0-alpha.5
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/README.md +8 -8
- package/README_de.md +8 -8
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/servercommander/__init__.py +1 -1
- package/src/servercommander/deploy.py +24 -0
- package/src/servercommander/i18n.py +152 -0
- package/src/servercommander/logs.py +28 -1
- package/src/servercommander/mail.py +17 -1
- package/src/servercommander/server.py +3 -2
package/README.md
CHANGED
|
@@ -12,9 +12,9 @@ German README: [README_de.md](README_de.md)
|
|
|
12
12
|
|
|
13
13
|
- Transport: stdio via the Python MCP SDK
|
|
14
14
|
- Package status: public alpha package under `ellmos-ai`
|
|
15
|
-
- Current core: MCP tool listing, MCP tool dispatch, config loading,
|
|
16
|
-
- Safe alpha handlers: `sc_deploy` builds local SHA256 manifests in dry-run mode; `sc_mail_*`
|
|
17
|
-
- i18n: localized MCP tool descriptions for `en`, `de`, `es`, `zh`, `ja`, `ru` with English fallback
|
|
15
|
+
- Current core: MCP tool listing, MCP tool dispatch, config loading, HTTP health checks, richer access-log analysis
|
|
16
|
+
- Safe alpha handlers: `sc_deploy` builds local SHA256 manifests and configuration diagnostics in dry-run mode; `sc_mail_*` reports mail configuration gaps without IMAP/SMTP operations
|
|
17
|
+
- i18n: localized MCP tool descriptions, input-schema field descriptions, and unknown-tool errors for `en`, `de`, `es`, `zh`, `ja`, `ru` with English fallback
|
|
18
18
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
@@ -66,10 +66,10 @@ Secrets should be referenced through environment variables, for example `$MAIL_P
|
|
|
66
66
|
## Tools
|
|
67
67
|
|
|
68
68
|
- `sc_health_check`: checks HTTP endpoints and reports status code plus latency
|
|
69
|
-
- `sc_logs_analyze`: analyzes Apache/Nginx access logs from inline text or a local file
|
|
70
|
-
- `sc_deploy`: creates a deployment plan with a local SHA256 manifest, but does not upload yet
|
|
71
|
-
- `sc_deploy_status`: shows configured deploy profiles and the current alpha history status
|
|
72
|
-
- `sc_mail_list`, `sc_mail_read`, `sc_mail_send`, `sc_mail_search`: safe alpha status responses
|
|
69
|
+
- `sc_logs_analyze`: analyzes Apache/Nginx access logs from inline text or a local file, including status classes, bytes, referers, error paths, and suspicious request markers
|
|
70
|
+
- `sc_deploy`: creates a deployment plan with a local SHA256 manifest and profile diagnostics, but does not upload yet
|
|
71
|
+
- `sc_deploy_status`: shows configured deploy profiles, selected-profile diagnostics, and the current alpha history status
|
|
72
|
+
- `sc_mail_list`, `sc_mail_read`, `sc_mail_send`, `sc_mail_search`: safe alpha status responses with mail configuration diagnostics and no IMAP/SMTP connections
|
|
73
73
|
|
|
74
74
|
## Development
|
|
75
75
|
|
|
@@ -81,4 +81,4 @@ npm run smoke
|
|
|
81
81
|
npm pack --dry-run
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
Next useful step:
|
|
84
|
+
Next useful step: add explicitly configured execution adapters for SFTP and IMAP/SMTP, keep dry-run defaults, and extend log analysis with persisted reports.
|
package/README_de.md
CHANGED
|
@@ -12,9 +12,9 @@ Englische Standard-README: [README.md](README.md)
|
|
|
12
12
|
|
|
13
13
|
- Transport: stdio über das Python-MCP-SDK
|
|
14
14
|
- Paketstatus: öffentliches Alpha-Paket unter `ellmos-ai`
|
|
15
|
-
- Aktiver Kern: MCP-Tool-Liste, MCP-Tool-Dispatch, Config-Lader,
|
|
16
|
-
- Sichere Alpha-Handler: `sc_deploy` erstellt lokale SHA256-Manifeste im Dry-run, `sc_mail_*`
|
|
17
|
-
- i18n: lokalisierte MCP-Tool-Beschreibungen für `en`, `de`, `es`, `zh`, `ja`, `ru` mit Englisch-Fallback
|
|
15
|
+
- Aktiver Kern: MCP-Tool-Liste, MCP-Tool-Dispatch, Config-Lader, HTTP-Health-Checks, erweiterte Access-Log-Analyse
|
|
16
|
+
- Sichere Alpha-Handler: `sc_deploy` erstellt lokale SHA256-Manifeste und Konfigurationsdiagnosen im Dry-run, `sc_mail_*` meldet Mail-Konfigurationslücken ohne IMAP/SMTP-Aktionen
|
|
17
|
+
- i18n: lokalisierte MCP-Tool-Beschreibungen, Input-Schema-Feldbeschreibungen und Unknown-Tool-Fehler für `en`, `de`, `es`, `zh`, `ja`, `ru` mit Englisch-Fallback
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
@@ -66,10 +66,10 @@ Secrets sollen als Umgebungsvariablen referenziert werden, zum Beispiel `$MAIL_P
|
|
|
66
66
|
## Tools
|
|
67
67
|
|
|
68
68
|
- `sc_health_check`: prüft HTTP-Endpunkte und meldet Status-Code plus Latenz
|
|
69
|
-
- `sc_logs_analyze`: analysiert Apache-/Nginx-Access-Logs aus Text oder Datei
|
|
70
|
-
- `sc_deploy`: erstellt einen Deployment-Plan mit lokalem SHA256-Manifest, führt aber noch keinen Upload aus
|
|
71
|
-
- `sc_deploy_status`: zeigt konfigurierte Deploy-Profile und den aktuellen Alpha-History-Status
|
|
72
|
-
- `sc_mail_list`, `sc_mail_read`, `sc_mail_send`, `sc_mail_search`: sichere Alpha-Statusantworten ohne IMAP/SMTP-Verbindung
|
|
69
|
+
- `sc_logs_analyze`: analysiert Apache-/Nginx-Access-Logs aus Text oder Datei, inklusive Statusklassen, Bytes, Referern, Fehlerpfaden und verdächtigen Request-Markern
|
|
70
|
+
- `sc_deploy`: erstellt einen Deployment-Plan mit lokalem SHA256-Manifest und Profildiagnose, führt aber noch keinen Upload aus
|
|
71
|
+
- `sc_deploy_status`: zeigt konfigurierte Deploy-Profile, ausgewählte Profildiagnosen und den aktuellen Alpha-History-Status
|
|
72
|
+
- `sc_mail_list`, `sc_mail_read`, `sc_mail_send`, `sc_mail_search`: sichere Alpha-Statusantworten mit Mail-Konfigurationsdiagnosen und ohne IMAP/SMTP-Verbindung
|
|
73
73
|
|
|
74
74
|
## Entwicklung
|
|
75
75
|
|
|
@@ -81,4 +81,4 @@ npm run smoke
|
|
|
81
81
|
npm pack --dry-run
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
Der nächste sinnvolle Schritt ist
|
|
84
|
+
Der nächste sinnvolle Schritt ist, explizit konfigurierte Ausführungsadapter für SFTP und IMAP/SMTP zu ergänzen, Dry-run als Standard beizubehalten und Log-Analysen um persistierte Reports zu erweitern.
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -33,6 +33,7 @@ async def sc_deploy(
|
|
|
33
33
|
"local_path": local_path or profile_config.get("local_path"),
|
|
34
34
|
"remote_path": remote_path or profile_config.get("remote_path"),
|
|
35
35
|
"protocol": profile_config.get("protocol", "sftp"),
|
|
36
|
+
"port": profile_config.get("port", 22),
|
|
36
37
|
}
|
|
37
38
|
missing = [key for key in ("host", "user", "local_path", "remote_path") if not plan.get(key)]
|
|
38
39
|
manifest = _build_manifest(plan["local_path"]) if plan.get("local_path") else None
|
|
@@ -43,6 +44,7 @@ async def sc_deploy(
|
|
|
43
44
|
"missing": missing,
|
|
44
45
|
"plan": plan,
|
|
45
46
|
"manifest": manifest,
|
|
47
|
+
"diagnostics": _deploy_diagnostics(profile_config, plan, manifest),
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
|
|
@@ -58,6 +60,7 @@ async def sc_deploy_status(
|
|
|
58
60
|
"message": "Deployment history storage is not implemented yet.",
|
|
59
61
|
"profiles": profiles,
|
|
60
62
|
"selected_profile": selected,
|
|
63
|
+
"diagnostics": _deploy_diagnostics(selected or {}, selected or {}, None) if profile else None,
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
|
|
@@ -98,3 +101,24 @@ def _file_entry(path: Path, root: Path) -> dict[str, Any]:
|
|
|
98
101
|
"size": path.stat().st_size,
|
|
99
102
|
"sha256": digest.hexdigest(),
|
|
100
103
|
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _deploy_diagnostics(profile_config: dict[str, Any], plan: dict[str, Any], manifest: dict[str, Any] | None) -> dict[str, Any]:
|
|
107
|
+
protocol = str(plan.get("protocol") or "sftp").lower()
|
|
108
|
+
auth_methods = []
|
|
109
|
+
if profile_config.get("key_path"):
|
|
110
|
+
auth_methods.append("key_path")
|
|
111
|
+
if profile_config.get("password"):
|
|
112
|
+
auth_methods.append("password")
|
|
113
|
+
if not auth_methods:
|
|
114
|
+
auth_methods.append("agent_or_prompt")
|
|
115
|
+
local_status = manifest.get("status") if manifest else "not_checked"
|
|
116
|
+
return {
|
|
117
|
+
"protocol_supported": protocol == "sftp",
|
|
118
|
+
"protocol": protocol,
|
|
119
|
+
"port": int(plan.get("port") or 22),
|
|
120
|
+
"auth_methods_configured": auth_methods,
|
|
121
|
+
"local_status": local_status,
|
|
122
|
+
"execution_enabled": False,
|
|
123
|
+
"next_step": "Review the dry-run plan and enable a future SFTP executor only after credential handling is finalized.",
|
|
124
|
+
}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from copy import deepcopy
|
|
5
6
|
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
6
8
|
|
|
7
9
|
DEFAULT_LOCALE = "en"
|
|
8
10
|
SUPPORTED_LOCALES = ("en", "de", "es", "zh", "ja", "ru")
|
|
@@ -19,6 +21,132 @@ ALIASES = {
|
|
|
19
21
|
"zh-hans": "zh",
|
|
20
22
|
"ja-jp": "ja",
|
|
21
23
|
"ru-ru": "ru",
|
|
24
|
+
"ru-by": "ru",
|
|
25
|
+
"zh-sg": "zh",
|
|
26
|
+
"zh-hk": "zh",
|
|
27
|
+
"zh-tw": "zh",
|
|
28
|
+
"es-419": "es",
|
|
29
|
+
"es-ar": "es",
|
|
30
|
+
"es-cl": "es",
|
|
31
|
+
"es-co": "es",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
DEFAULT_SCHEMA_DESCRIPTIONS: dict[str, str] = {
|
|
35
|
+
"body": "Email body text.",
|
|
36
|
+
"dry_run": "Build the plan without executing deployment.",
|
|
37
|
+
"endpoints": "HTTP endpoint URLs to check.",
|
|
38
|
+
"folder": "Mail folder name.",
|
|
39
|
+
"format": "Log format hint.",
|
|
40
|
+
"limit": "Maximum number of results.",
|
|
41
|
+
"local_path": "Local source path.",
|
|
42
|
+
"log_path": "Local access-log file path.",
|
|
43
|
+
"log_text": "Inline access-log text.",
|
|
44
|
+
"message_id": "Message identifier.",
|
|
45
|
+
"profile": "Deployment profile name.",
|
|
46
|
+
"query": "Search query.",
|
|
47
|
+
"remote_path": "Remote target path.",
|
|
48
|
+
"subject": "Email subject.",
|
|
49
|
+
"timeout": "Request timeout in seconds.",
|
|
50
|
+
"to": "Email recipient.",
|
|
51
|
+
"top_paths": "Number of top paths to include.",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
SCHEMA_TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
55
|
+
"de": {
|
|
56
|
+
"body": "Textkörper der E-Mail.",
|
|
57
|
+
"dry_run": "Plan erstellen, ohne das Deployment auszuführen.",
|
|
58
|
+
"endpoints": "Zu prüfende HTTP-Endpunkt-URLs.",
|
|
59
|
+
"folder": "Name des Mail-Ordners.",
|
|
60
|
+
"format": "Hinweis auf das Logformat.",
|
|
61
|
+
"limit": "Maximale Anzahl von Ergebnissen.",
|
|
62
|
+
"local_path": "Lokaler Quellpfad.",
|
|
63
|
+
"log_path": "Lokaler Pfad zur Access-Log-Datei.",
|
|
64
|
+
"log_text": "Access-Log-Text direkt als Eingabe.",
|
|
65
|
+
"message_id": "Nachrichtenkennung.",
|
|
66
|
+
"profile": "Name des Deployment-Profils.",
|
|
67
|
+
"query": "Suchanfrage.",
|
|
68
|
+
"remote_path": "Entfernter Zielpfad.",
|
|
69
|
+
"subject": "E-Mail-Betreff.",
|
|
70
|
+
"timeout": "Request-Timeout in Sekunden.",
|
|
71
|
+
"to": "E-Mail-Empfänger.",
|
|
72
|
+
"top_paths": "Anzahl der wichtigsten Pfade.",
|
|
73
|
+
},
|
|
74
|
+
"es": {
|
|
75
|
+
"body": "Texto del cuerpo del correo.",
|
|
76
|
+
"dry_run": "Crear el plan sin ejecutar el despliegue.",
|
|
77
|
+
"endpoints": "URLs de endpoints HTTP a comprobar.",
|
|
78
|
+
"folder": "Nombre de la carpeta de correo.",
|
|
79
|
+
"format": "Pista sobre el formato del log.",
|
|
80
|
+
"limit": "Número máximo de resultados.",
|
|
81
|
+
"local_path": "Ruta local de origen.",
|
|
82
|
+
"log_path": "Ruta local del archivo de access log.",
|
|
83
|
+
"log_text": "Texto de access log en línea.",
|
|
84
|
+
"message_id": "Identificador del mensaje.",
|
|
85
|
+
"profile": "Nombre del perfil de despliegue.",
|
|
86
|
+
"query": "Consulta de búsqueda.",
|
|
87
|
+
"remote_path": "Ruta remota de destino.",
|
|
88
|
+
"subject": "Asunto del correo.",
|
|
89
|
+
"timeout": "Timeout de la petición en segundos.",
|
|
90
|
+
"to": "Destinatario del correo.",
|
|
91
|
+
"top_paths": "Cantidad de rutas principales a incluir.",
|
|
92
|
+
},
|
|
93
|
+
"zh": {
|
|
94
|
+
"body": "邮件正文。",
|
|
95
|
+
"dry_run": "只生成计划,不执行部署。",
|
|
96
|
+
"endpoints": "要检查的 HTTP 端点 URL。",
|
|
97
|
+
"folder": "邮件文件夹名称。",
|
|
98
|
+
"format": "日志格式提示。",
|
|
99
|
+
"limit": "最大结果数量。",
|
|
100
|
+
"local_path": "本地源路径。",
|
|
101
|
+
"log_path": "本地访问日志文件路径。",
|
|
102
|
+
"log_text": "内联访问日志文本。",
|
|
103
|
+
"message_id": "消息标识符。",
|
|
104
|
+
"profile": "部署配置名称。",
|
|
105
|
+
"query": "搜索查询。",
|
|
106
|
+
"remote_path": "远程目标路径。",
|
|
107
|
+
"subject": "邮件主题。",
|
|
108
|
+
"timeout": "请求超时时间(秒)。",
|
|
109
|
+
"to": "邮件收件人。",
|
|
110
|
+
"top_paths": "要包含的热门路径数量。",
|
|
111
|
+
},
|
|
112
|
+
"ja": {
|
|
113
|
+
"body": "メール本文。",
|
|
114
|
+
"dry_run": "デプロイを実行せずに計画を作成します。",
|
|
115
|
+
"endpoints": "確認する HTTP エンドポイント URL。",
|
|
116
|
+
"folder": "メールフォルダ名。",
|
|
117
|
+
"format": "ログ形式のヒント。",
|
|
118
|
+
"limit": "結果の最大数。",
|
|
119
|
+
"local_path": "ローカルのソースパス。",
|
|
120
|
+
"log_path": "ローカルのアクセスログファイルパス。",
|
|
121
|
+
"log_text": "インラインのアクセスログテキスト。",
|
|
122
|
+
"message_id": "メッセージ識別子。",
|
|
123
|
+
"profile": "デプロイプロファイル名。",
|
|
124
|
+
"query": "検索クエリ。",
|
|
125
|
+
"remote_path": "リモートの宛先パス。",
|
|
126
|
+
"subject": "メール件名。",
|
|
127
|
+
"timeout": "リクエストタイムアウト(秒)。",
|
|
128
|
+
"to": "メール受信者。",
|
|
129
|
+
"top_paths": "含める上位パスの数。",
|
|
130
|
+
},
|
|
131
|
+
"ru": {
|
|
132
|
+
"body": "Текст письма.",
|
|
133
|
+
"dry_run": "Создать план без выполнения деплоя.",
|
|
134
|
+
"endpoints": "URL HTTP endpoints для проверки.",
|
|
135
|
+
"folder": "Название почтовой папки.",
|
|
136
|
+
"format": "Подсказка формата лога.",
|
|
137
|
+
"limit": "Максимальное количество результатов.",
|
|
138
|
+
"local_path": "Локальный исходный путь.",
|
|
139
|
+
"log_path": "Локальный путь к access-log файлу.",
|
|
140
|
+
"log_text": "Текст access-log напрямую.",
|
|
141
|
+
"message_id": "Идентификатор сообщения.",
|
|
142
|
+
"profile": "Название профиля деплоя.",
|
|
143
|
+
"query": "Поисковый запрос.",
|
|
144
|
+
"remote_path": "Удаленный целевой путь.",
|
|
145
|
+
"subject": "Тема письма.",
|
|
146
|
+
"timeout": "Таймаут запроса в секундах.",
|
|
147
|
+
"to": "Получатель письма.",
|
|
148
|
+
"top_paths": "Количество популярных путей.",
|
|
149
|
+
},
|
|
22
150
|
}
|
|
23
151
|
|
|
24
152
|
TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
@@ -31,6 +159,7 @@ TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
|
31
159
|
"tool.sc_mail_search": "Alpha-Mail-Status für die Mail-Suche.",
|
|
32
160
|
"tool.sc_logs_analyze": "Analysiert Apache-/Nginx-Access-Logs aus Text oder Datei.",
|
|
33
161
|
"tool.sc_health_check": "Prüft HTTP-Endpunkte und meldet Status-Codes plus Latenz.",
|
|
162
|
+
"error.unknown_tool": "Unbekanntes ServerCommander-Tool: {name}",
|
|
34
163
|
},
|
|
35
164
|
"es": {
|
|
36
165
|
"tool.sc_deploy": "Crea un plan de despliegue seguro. Alpha: solo dry-run.",
|
|
@@ -41,6 +170,7 @@ TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
|
41
170
|
"tool.sc_mail_search": "Estado alpha para buscar correo.",
|
|
42
171
|
"tool.sc_logs_analyze": "Analiza logs de acceso Apache/Nginx desde texto o archivo.",
|
|
43
172
|
"tool.sc_health_check": "Comprueba endpoints HTTP y devuelve código de estado y latencia.",
|
|
173
|
+
"error.unknown_tool": "Herramienta ServerCommander desconocida: {name}",
|
|
44
174
|
},
|
|
45
175
|
"zh": {
|
|
46
176
|
"tool.sc_deploy": "创建安全部署计划。Alpha 版仅支持 dry-run。",
|
|
@@ -51,6 +181,7 @@ TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
|
51
181
|
"tool.sc_mail_search": "用于搜索邮件的 Alpha 状态。",
|
|
52
182
|
"tool.sc_logs_analyze": "从文本或文件分析 Apache/Nginx 访问日志。",
|
|
53
183
|
"tool.sc_health_check": "检查 HTTP 端点并返回状态码和延迟。",
|
|
184
|
+
"error.unknown_tool": "未知 ServerCommander 工具:{name}",
|
|
54
185
|
},
|
|
55
186
|
"ja": {
|
|
56
187
|
"tool.sc_deploy": "安全なデプロイ計画を作成します。Alpha では dry-run のみです。",
|
|
@@ -61,6 +192,7 @@ TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
|
61
192
|
"tool.sc_mail_search": "メール検索用の Alpha 状態を返します。",
|
|
62
193
|
"tool.sc_logs_analyze": "テキストまたはファイルから Apache/Nginx アクセスログを分析します。",
|
|
63
194
|
"tool.sc_health_check": "HTTP エンドポイントを確認し、ステータスコードとレイテンシを返します。",
|
|
195
|
+
"error.unknown_tool": "不明な ServerCommander ツール: {name}",
|
|
64
196
|
},
|
|
65
197
|
"ru": {
|
|
66
198
|
"tool.sc_deploy": "Создает безопасный план деплоя. Alpha: только dry-run.",
|
|
@@ -71,6 +203,7 @@ TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
|
71
203
|
"tool.sc_mail_search": "Alpha-статус поиска почты.",
|
|
72
204
|
"tool.sc_logs_analyze": "Анализирует access-логи Apache/Nginx из текста или файла.",
|
|
73
205
|
"tool.sc_health_check": "Проверяет HTTP endpoints и возвращает коды статуса и задержку.",
|
|
206
|
+
"error.unknown_tool": "Неизвестный инструмент ServerCommander: {name}",
|
|
74
207
|
},
|
|
75
208
|
}
|
|
76
209
|
|
|
@@ -96,3 +229,22 @@ class I18n:
|
|
|
96
229
|
if self.locale == DEFAULT_LOCALE:
|
|
97
230
|
return default or key
|
|
98
231
|
return TRANSLATIONS.get(self.locale, {}).get(key) or default or key
|
|
232
|
+
|
|
233
|
+
def localize_schema(self, schema: dict[str, Any]) -> dict[str, Any]:
|
|
234
|
+
"""Return a copy of a JSON schema with localized property descriptions."""
|
|
235
|
+
localized = deepcopy(schema)
|
|
236
|
+
properties = localized.get("properties")
|
|
237
|
+
if isinstance(properties, dict):
|
|
238
|
+
for name, property_schema in properties.items():
|
|
239
|
+
if not isinstance(property_schema, dict):
|
|
240
|
+
continue
|
|
241
|
+
description = self._schema_description(name, property_schema.get("description"))
|
|
242
|
+
if description:
|
|
243
|
+
property_schema["description"] = description
|
|
244
|
+
return localized
|
|
245
|
+
|
|
246
|
+
def _schema_description(self, name: str, default: str | None = None) -> str | None:
|
|
247
|
+
fallback = default or DEFAULT_SCHEMA_DESCRIPTIONS.get(name)
|
|
248
|
+
if self.locale == DEFAULT_LOCALE:
|
|
249
|
+
return fallback
|
|
250
|
+
return SCHEMA_TRANSLATIONS.get(self.locale, {}).get(name) or fallback
|
|
@@ -18,6 +18,7 @@ LOG_PATTERN = re.compile(
|
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
BOT_MARKERS = ("bot", "crawler", "spider", "slurp", "bingpreview")
|
|
21
|
+
SUSPICIOUS_MARKERS = ("../", "%2e%2e", "/wp-admin", "/.env", "/phpmyadmin", "/admin", "/login")
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
async def sc_logs_analyze(
|
|
@@ -36,8 +37,13 @@ async def sc_logs_analyze(
|
|
|
36
37
|
status_classes: Counter[str] = Counter()
|
|
37
38
|
method_counts: Counter[str] = Counter()
|
|
38
39
|
path_counts: Counter[str] = Counter()
|
|
40
|
+
error_path_counts: Counter[str] = Counter()
|
|
39
41
|
agent_counts: Counter[str] = Counter()
|
|
42
|
+
referer_counts: Counter[str] = Counter()
|
|
43
|
+
hosts: set[str] = set()
|
|
44
|
+
total_bytes = 0
|
|
40
45
|
bot_requests = 0
|
|
46
|
+
suspicious_requests = 0
|
|
41
47
|
parsed_lines = 0
|
|
42
48
|
|
|
43
49
|
for line in lines:
|
|
@@ -45,28 +51,49 @@ async def sc_logs_analyze(
|
|
|
45
51
|
if not match:
|
|
46
52
|
continue
|
|
47
53
|
parsed_lines += 1
|
|
54
|
+
hosts.add(match.group("host"))
|
|
48
55
|
status = match.group("status")
|
|
49
56
|
status_counts[status] += 1
|
|
50
57
|
status_classes[f"{status[0]}xx"] += 1
|
|
51
58
|
method_counts[match.group("method")] += 1
|
|
52
|
-
|
|
59
|
+
path = match.group("path")
|
|
60
|
+
path_counts[path] += 1
|
|
61
|
+
if int(status) >= 400:
|
|
62
|
+
error_path_counts[path] += 1
|
|
63
|
+
size = match.group("size")
|
|
64
|
+
if size.isdigit():
|
|
65
|
+
total_bytes += int(size)
|
|
66
|
+
referer = match.group("referer") or ""
|
|
67
|
+
if referer and referer != "-":
|
|
68
|
+
referer_counts[referer] += 1
|
|
53
69
|
agent = match.group("agent") or ""
|
|
54
70
|
if agent:
|
|
55
71
|
agent_counts[agent] += 1
|
|
56
72
|
if any(marker in agent.lower() for marker in BOT_MARKERS):
|
|
57
73
|
bot_requests += 1
|
|
74
|
+
if any(marker in path.lower() for marker in SUSPICIOUS_MARKERS):
|
|
75
|
+
suspicious_requests += 1
|
|
76
|
+
|
|
77
|
+
error_count = sum(count for status, count in status_counts.items() if int(status) >= 400)
|
|
78
|
+
error_rate = round(error_count / parsed_lines, 4) if parsed_lines else 0.0
|
|
58
79
|
|
|
59
80
|
return {
|
|
60
81
|
"status": "ok",
|
|
61
82
|
"total_lines": len(lines),
|
|
62
83
|
"parsed_lines": parsed_lines,
|
|
63
84
|
"unparsed_lines": len(lines) - parsed_lines,
|
|
85
|
+
"unique_hosts": len(hosts),
|
|
86
|
+
"total_bytes": total_bytes,
|
|
87
|
+
"error_rate": error_rate,
|
|
64
88
|
"status_counts": dict(status_counts),
|
|
65
89
|
"status_classes": dict(status_classes),
|
|
66
90
|
"method_counts": dict(method_counts),
|
|
67
91
|
"top_paths": path_counts.most_common(int(top_paths)),
|
|
92
|
+
"top_error_paths": error_path_counts.most_common(int(top_paths)),
|
|
93
|
+
"top_referers": referer_counts.most_common(10),
|
|
68
94
|
"top_agents": agent_counts.most_common(10),
|
|
69
95
|
"bot_requests": bot_requests,
|
|
96
|
+
"suspicious_requests": suspicious_requests,
|
|
70
97
|
}
|
|
71
98
|
|
|
72
99
|
|
|
@@ -40,11 +40,27 @@ def _mail_alpha_response(config: ServerCommanderConfig, action: str, request: di
|
|
|
40
40
|
mail = config.mail
|
|
41
41
|
username = resolve_env_value(mail.get("username", ""))
|
|
42
42
|
password = resolve_env_value(mail.get("password", ""))
|
|
43
|
-
|
|
43
|
+
checks = {
|
|
44
|
+
"imap_host": bool(mail.get("imap_host")),
|
|
45
|
+
"smtp_host": bool(mail.get("smtp_host")),
|
|
46
|
+
"username": bool(username),
|
|
47
|
+
"password": bool(password),
|
|
48
|
+
}
|
|
49
|
+
configured = all(checks.values())
|
|
50
|
+
missing = [key for key, ok in checks.items() if not ok]
|
|
44
51
|
return {
|
|
45
52
|
"status": "not_implemented",
|
|
46
53
|
"action": action,
|
|
47
54
|
"configured": configured,
|
|
55
|
+
"missing": missing,
|
|
56
|
+
"checks": checks,
|
|
57
|
+
"capabilities": {
|
|
58
|
+
"list": False,
|
|
59
|
+
"read": False,
|
|
60
|
+
"send": False,
|
|
61
|
+
"search": False,
|
|
62
|
+
"config_diagnostics": True,
|
|
63
|
+
},
|
|
48
64
|
"request": request,
|
|
49
65
|
"message": "IMAP/SMTP execution is not implemented in the alpha server.",
|
|
50
66
|
}
|
|
@@ -39,7 +39,7 @@ class ServerCommanderRegistry:
|
|
|
39
39
|
types.Tool(
|
|
40
40
|
name=tool.name,
|
|
41
41
|
description=self.i18n.t(f"tool.{tool.name}", tool.description),
|
|
42
|
-
inputSchema=tool.input_schema,
|
|
42
|
+
inputSchema=self.i18n.localize_schema(tool.input_schema),
|
|
43
43
|
)
|
|
44
44
|
for tool in self._tools
|
|
45
45
|
]
|
|
@@ -47,7 +47,8 @@ class ServerCommanderRegistry:
|
|
|
47
47
|
async def call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
48
48
|
handler = self._handlers.get(name)
|
|
49
49
|
if handler is None:
|
|
50
|
-
|
|
50
|
+
message = self.i18n.t("error.unknown_tool", "Unknown ServerCommander tool: {name}").format(name=name)
|
|
51
|
+
raise ValueError(message)
|
|
51
52
|
return await handler(**(arguments or {}))
|
|
52
53
|
|
|
53
54
|
|