winterm-mcp 0.1.4__tar.gz → 0.1.5__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winterm-mcp
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: A Model Context Protocol (MCP) service for executing Windows terminal commands asynchronously
5
5
  Author-email: winterm-mcp contributors <maintainer@example.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "winterm-mcp"
7
- version = "0.1.4"
7
+ version = "0.1.5"
8
8
  description = "A Model Context Protocol (MCP) service for executing Windows terminal commands asynchronously"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,9 @@
1
+ """
2
+ winterm-mcp - Windows Terminal MCP Service
3
+ """
4
+
5
+ from .service import __version__, get_version, setup_logging
6
+
7
+ __author__ = "winterm-mcp contributors"
8
+
9
+ __all__ = ["__version__", "get_version", "setup_logging"]
@@ -0,0 +1,42 @@
1
+ """
2
+ winterm-mcp 主入口
3
+ """
4
+
5
+ from .server import app, init_service
6
+ from .service import RunCmdService, setup_logging, __version__
7
+ import logging
8
+ import os
9
+ import tempfile
10
+
11
+
12
+ def main():
13
+ """
14
+ 主函数,启动 MCP 服务器
15
+ """
16
+ # 初始化日志
17
+ setup_logging(logging.INFO)
18
+
19
+ logger = logging.getLogger("winterm-mcp")
20
+
21
+ # 获取日志文件路径并记录
22
+ log_file = os.environ.get("WINTERM_LOG_FILE") or os.path.join(
23
+ tempfile.gettempdir(), "winterm-mcp.log"
24
+ )
25
+
26
+ logger.info("=" * 60)
27
+ logger.info(f"winterm-mcp v{__version__} starting...")
28
+ logger.info(f"Log file: {log_file}")
29
+ logger.info(f"Temp dir: {tempfile.gettempdir()}")
30
+ logger.info(f"Working dir: {os.getcwd()}")
31
+ logger.info(f"WINTERM_POWERSHELL_PATH: {os.environ.get('WINTERM_POWERSHELL_PATH', '(not set)')}")
32
+ logger.info("=" * 60)
33
+
34
+ service = RunCmdService()
35
+ init_service(service)
36
+
37
+ logger.info("Service initialized, starting MCP server...")
38
+ app.run()
39
+
40
+
41
+ if __name__ == "__main__":
42
+ main()
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Annotated, Optional, Dict, Any
4
4
  from mcp.server.fastmcp import FastMCP
5
- from .service import RunCmdService
5
+ from .service import RunCmdService, get_version, setup_logging, __version__
6
6
  from pydantic import Field
7
7
 
8
8
  CommandStr = Annotated[
@@ -124,3 +124,50 @@ def query_command_status(token: str) -> Dict[str, Any]:
124
124
  return result
125
125
  except Exception as e:
126
126
  return {"error": str(e)}
127
+
128
+
129
+ @app.tool(
130
+ name="get_version",
131
+ description="获取 winterm-mcp 服务的版本信息和运行状态。",
132
+ annotations={
133
+ "title": "版本信息",
134
+ "readOnlyHint": True,
135
+ "destructiveHint": False,
136
+ "idempotentHint": True,
137
+ "openWorldHint": False,
138
+ },
139
+ )
140
+ def get_version_tool() -> Dict[str, Any]:
141
+ """
142
+ 获取 winterm-mcp 版本信息
143
+
144
+ Returns:
145
+ 包含版本号和服务状态的字典
146
+ """
147
+ import os
148
+ import sys
149
+
150
+ try:
151
+ # 尝试获取 PowerShell 路径信息
152
+ ps_path = None
153
+ ps_error = None
154
+ try:
155
+ from .service import _find_powershell
156
+ ps_path = _find_powershell()
157
+ except FileNotFoundError as e:
158
+ ps_error = str(e)
159
+
160
+ return {
161
+ "version": get_version(),
162
+ "service_status": "running",
163
+ "python_version": sys.version,
164
+ "platform": sys.platform,
165
+ "powershell_path": ps_path,
166
+ "powershell_error": ps_error,
167
+ "env": {
168
+ "WINTERM_POWERSHELL_PATH": os.environ.get("WINTERM_POWERSHELL_PATH"),
169
+ "WINTERM_LOG_LEVEL": os.environ.get("WINTERM_LOG_LEVEL"),
170
+ }
171
+ }
172
+ except Exception as e:
173
+ return {"error": str(e), "version": __version__}
@@ -8,9 +8,65 @@ import uuid
8
8
  import time
9
9
  import os
10
10
  import shutil
11
+ import logging
11
12
  from datetime import datetime
12
13
  from typing import Dict, Optional, Any, List
13
14
 
15
+ # 版本号
16
+ __version__ = "0.1.5"
17
+
18
+ # 配置日志
19
+ logger = logging.getLogger("winterm-mcp")
20
+
21
+
22
+ def setup_logging(level: int = logging.INFO) -> None:
23
+ """
24
+ 配置日志输出
25
+
26
+ Args:
27
+ level: 日志级别,默认 INFO
28
+
29
+ 日志输出位置:
30
+ 1. 控制台 (stderr)
31
+ 2. 文件: %TEMP%/winterm-mcp.log 或 /tmp/winterm-mcp.log
32
+
33
+ 可通过环境变量配置:
34
+ - WINTERM_LOG_LEVEL: 日志级别 (DEBUG/INFO/WARNING/ERROR/CRITICAL)
35
+ - WINTERM_LOG_FILE: 自定义日志文件路径
36
+ """
37
+ import tempfile
38
+
39
+ formatter = logging.Formatter(
40
+ "[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s",
41
+ datefmt="%Y-%m-%d %H:%M:%S"
42
+ )
43
+
44
+ # 控制台输出
45
+ console_handler = logging.StreamHandler()
46
+ console_handler.setFormatter(formatter)
47
+ logger.addHandler(console_handler)
48
+
49
+ # 文件输出
50
+ log_file = os.environ.get("WINTERM_LOG_FILE")
51
+ if not log_file:
52
+ # 默认日志文件路径
53
+ log_file = os.path.join(tempfile.gettempdir(), "winterm-mcp.log")
54
+
55
+ try:
56
+ file_handler = logging.FileHandler(log_file, encoding="utf-8")
57
+ file_handler.setFormatter(formatter)
58
+ logger.addHandler(file_handler)
59
+ logger.info(f"Log file: {log_file}")
60
+ except Exception as e:
61
+ logger.warning(f"Failed to create log file {log_file}: {e}")
62
+
63
+ logger.setLevel(level)
64
+
65
+ # 检查环境变量设置日志级别
66
+ env_level = os.environ.get("WINTERM_LOG_LEVEL", "").upper()
67
+ if env_level in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"):
68
+ logger.setLevel(getattr(logging, env_level))
69
+
14
70
 
15
71
  # PowerShell 可执行文件的标准路径(按优先级排序)
16
72
  POWERSHELL_PATHS: List[str] = [
@@ -44,40 +100,67 @@ def _find_powershell() -> str:
44
100
  Raises:
45
101
  FileNotFoundError: 如果找不到 PowerShell
46
102
  """
103
+ logger.debug("Starting PowerShell path discovery...")
104
+
47
105
  # 1. 检查用户配置的环境变量
48
106
  custom_path = os.environ.get(ENV_POWERSHELL_PATH)
49
107
  if custom_path:
108
+ logger.debug(f"Found env var {ENV_POWERSHELL_PATH}={custom_path}")
50
109
  if os.path.isfile(custom_path):
110
+ logger.info(f"Using custom PowerShell path: {custom_path}")
51
111
  return custom_path
52
- # 用户配置了但路径无效,记录警告但继续查找
112
+ else:
113
+ logger.warning(
114
+ f"Custom PowerShell path not found: {custom_path}, "
115
+ "falling back to standard paths"
116
+ )
53
117
 
54
118
  # 2. 检查 Windows PowerShell 标准路径
55
119
  for path in POWERSHELL_PATHS:
120
+ logger.debug(f"Checking standard path: {path}")
56
121
  if os.path.isfile(path):
122
+ logger.info(f"Found Windows PowerShell: {path}")
57
123
  return path
58
124
 
59
125
  # 3. 检查 PowerShell Core 标准路径
60
126
  for path in PWSH_PATHS:
127
+ logger.debug(f"Checking PowerShell Core path: {path}")
61
128
  if os.path.isfile(path):
129
+ logger.info(f"Found PowerShell Core: {path}")
62
130
  return path
63
131
 
64
132
  # 4. 尝试 PATH 环境变量(兼容正常环境)
133
+ logger.debug("Checking PATH environment variable...")
65
134
  ps_path = shutil.which("powershell")
66
135
  if ps_path:
136
+ logger.info(f"Found PowerShell in PATH: {ps_path}")
67
137
  return ps_path
68
138
 
69
139
  pwsh_path = shutil.which("pwsh")
70
140
  if pwsh_path:
141
+ logger.info(f"Found pwsh in PATH: {pwsh_path}")
71
142
  return pwsh_path
72
143
 
73
144
  # 所有方法都失败
74
145
  checked_paths = POWERSHELL_PATHS + PWSH_PATHS
75
- raise FileNotFoundError(
146
+ error_msg = (
76
147
  f"PowerShell not found. "
77
148
  f"Set {ENV_POWERSHELL_PATH} environment variable or "
78
149
  f"ensure PowerShell is installed. "
79
150
  f"Checked paths: {', '.join(checked_paths)}"
80
151
  )
152
+ logger.error(error_msg)
153
+ raise FileNotFoundError(error_msg)
154
+
155
+
156
+ def get_version() -> str:
157
+ """
158
+ 获取 winterm-mcp 版本号
159
+
160
+ Returns:
161
+ 版本号字符串
162
+ """
163
+ return __version__
81
164
 
82
165
 
83
166
  class RunCmdService:
@@ -104,6 +187,7 @@ class RunCmdService:
104
187
  """
105
188
  if self._powershell_path is None:
106
189
  self._powershell_path = _find_powershell()
190
+ logger.debug(f"PowerShell path cached: {self._powershell_path}")
107
191
  return self._powershell_path
108
192
 
109
193
  def run_command(
@@ -126,6 +210,12 @@ class RunCmdService:
126
210
  命令执行的token
127
211
  """
128
212
  token = str(uuid.uuid4())
213
+
214
+ logger.info(
215
+ f"Submitting command: token={token}, shell={shell_type}, "
216
+ f"timeout={timeout}, cwd={working_directory}"
217
+ )
218
+ logger.debug(f"Command content: {command[:100]}{'...' if len(command) > 100 else ''}")
129
219
 
130
220
  cmd_info = {
131
221
  "token": token,
@@ -167,6 +257,7 @@ class RunCmdService:
167
257
  """
168
258
  try:
169
259
  start_time = time.time()
260
+ logger.debug(f"[{token}] Starting command execution...")
170
261
 
171
262
  with self.lock:
172
263
  if token in self.commands:
@@ -177,6 +268,7 @@ class RunCmdService:
177
268
  if shell_type == "powershell":
178
269
  # 使用绝对路径调用 PowerShell,避免 PATH 环境变量限制
179
270
  ps_path = self._get_powershell_path()
271
+ logger.info(f"[{token}] Using PowerShell: {ps_path}")
180
272
  cmd_args = [
181
273
  ps_path,
182
274
  "-NoProfile",
@@ -188,8 +280,11 @@ class RunCmdService:
188
280
  command,
189
281
  ]
190
282
  else:
283
+ logger.debug(f"[{token}] Using cmd.exe")
191
284
  cmd_args = ["cmd", "/c", command]
192
285
 
286
+ logger.debug(f"[{token}] Executing: {cmd_args}")
287
+
193
288
  result = subprocess.run(
194
289
  cmd_args,
195
290
  capture_output=True,
@@ -201,6 +296,13 @@ class RunCmdService:
201
296
  )
202
297
 
203
298
  execution_time = time.time() - start_time
299
+
300
+ logger.info(
301
+ f"[{token}] Command completed: exit_code={result.returncode}, "
302
+ f"time={execution_time:.3f}s"
303
+ )
304
+ logger.debug(f"[{token}] stdout: {result.stdout[:200] if result.stdout else '(empty)'}")
305
+ logger.debug(f"[{token}] stderr: {result.stderr[:200] if result.stderr else '(empty)'}")
204
306
 
205
307
  with self.lock:
206
308
  if token in self.commands:
@@ -216,6 +318,7 @@ class RunCmdService:
216
318
 
217
319
  except FileNotFoundError as e:
218
320
  execution_time = time.time() - start_time
321
+ logger.error(f"[{token}] PowerShell not found: {e}")
219
322
  with self.lock:
220
323
  if token in self.commands:
221
324
  self.commands[token].update(
@@ -230,6 +333,7 @@ class RunCmdService:
230
333
  )
231
334
  except subprocess.TimeoutExpired:
232
335
  execution_time = time.time() - start_time
336
+ logger.warning(f"[{token}] Command timed out after {timeout}s")
233
337
  with self.lock:
234
338
  if token in self.commands:
235
339
  self.commands[token].update(
@@ -246,6 +350,7 @@ class RunCmdService:
246
350
  )
247
351
  except Exception as e:
248
352
  execution_time = time.time() - start_time
353
+ logger.error(f"[{token}] Command failed with exception: {e}")
249
354
  with self.lock:
250
355
  if token in self.commands:
251
356
  self.commands[token].update(
@@ -269,8 +374,11 @@ class RunCmdService:
269
374
  Returns:
270
375
  包含命令状态的字典
271
376
  """
377
+ logger.debug(f"Querying status for token: {token}")
378
+
272
379
  with self.lock:
273
380
  if token not in self.commands:
381
+ logger.warning(f"Token not found: {token}")
274
382
  return {
275
383
  "token": token,
276
384
  "status": "not_found",
@@ -278,6 +386,7 @@ class RunCmdService:
278
386
  }
279
387
 
280
388
  cmd_info = self.commands[token].copy()
389
+ logger.debug(f"[{token}] Status: {cmd_info['status']}")
281
390
 
282
391
  if cmd_info["status"] == "running":
283
392
  return {"token": cmd_info["token"], "status": "running"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winterm-mcp
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: A Model Context Protocol (MCP) service for executing Windows terminal commands asynchronously
5
5
  Author-email: winterm-mcp contributors <maintainer@example.com>
6
6
  License: MIT
@@ -1,6 +0,0 @@
1
- """
2
- winterm-mcp - Windows Terminal MCP Service
3
- """
4
-
5
- __version__ = "0.1.1"
6
- __author__ = "winterm-mcp contributors"
@@ -1,19 +0,0 @@
1
- """
2
- winterm-mcp 主入口
3
- """
4
-
5
- from .server import app, init_service
6
- from .service import RunCmdService
7
-
8
-
9
- def main():
10
- """
11
- 主函数,启动 MCP 服务器
12
- """
13
- service = RunCmdService()
14
- init_service(service)
15
- app.run()
16
-
17
-
18
- if __name__ == "__main__":
19
- main()
File without changes
File without changes
File without changes