ellmos-servercommander-mcp 0.1.0-alpha.3 → 0.1.0-alpha.4

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
@@ -14,7 +14,7 @@ German README: [README_de.md](README_de.md)
14
14
  - Package status: public alpha package under `ellmos-ai`
15
15
  - Current core: MCP tool listing, MCP tool dispatch, config loading, `sc_logs_analyze`, `sc_health_check`
16
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
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
 
package/README_de.md CHANGED
@@ -14,7 +14,7 @@ Englische Standard-README: [README.md](README.md)
14
14
  - Paketstatus: öffentliches Alpha-Paket unter `ellmos-ai`
15
15
  - Aktiver Kern: MCP-Tool-Liste, MCP-Tool-Dispatch, Config-Lader, `sc_logs_analyze`, `sc_health_check`
16
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
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
 
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.4",
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.0a4"
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.0a4"
@@ -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
@@ -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