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 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, `sc_logs_analyze`, `sc_health_check`
16
- - Safe alpha handlers: `sc_deploy` builds local SHA256 manifests in dry-run mode; `sc_mail_*` does not perform IMAP/SMTP operations yet
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 without IMAP/SMTP connections
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: extract real SFTP, IMAP/SMTP, and extended traffic-analysis modules from `.UMBRUCH` into credential-free adapters with local tests.
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, `sc_logs_analyze`, `sc_health_check`
16
- - Sichere Alpha-Handler: `sc_deploy` erstellt lokale SHA256-Manifeste im Dry-run, `sc_mail_*` führt noch keine IMAP/SMTP-Aktionen aus
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 die Extraktion der echten SFTP-, IMAP/SMTP- und erweiterten Traffic-Module aus `.UMBRUCH` in credential-freie Adapter mit lokalen Tests.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ellmos-servercommander-mcp",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.5",
4
4
  "description": "Alpha MCP server for server operations: deploy dry-runs, mail status, log analysis, and health checks.",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ellmos-servercommander-mcp"
7
- version = "0.1.0a3"
7
+ version = "0.1.0a5"
8
8
  description = "MCP server for server operations: deploy, mail, log analysis, health checks."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """ellmos-servercommander-mcp - server operations via MCP."""
2
2
 
3
- __version__ = "0.1.0a3"
3
+ __version__ = "0.1.0a5"
@@ -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
- path_counts[match.group("path")] += 1
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
- configured = bool(mail.get("imap_host") and mail.get("smtp_host") and username and password)
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
- raise ValueError(f"Unknown ServerCommander tool: {name}")
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