winterm-mcp 0.1.3__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.3
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
@@ -27,8 +27,8 @@ Dynamic: license-file
27
27
 
28
28
  # winterm-mcp
29
29
 
30
- **更新日期**: 2026-01-16
31
- **版本**: 0.1.1
30
+ **更新日期**: 2026-01-16
31
+ **版本**: 0.1.4
32
32
 
33
33
  Windows Terminal MCP Service - 专门支持 Windows 终端的异步命令执行工具。
34
34
 
@@ -58,8 +58,50 @@ pip install -e .
58
58
  ```json
59
59
  {
60
60
  "mcpServers": {
61
- "winterm-mcp": {
62
- "command": "winterm-mcp"
61
+ "winterm": {
62
+ "command": "uvx",
63
+ "args": [
64
+ "winterm-mcp"
65
+ ]
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### 环境配置
72
+
73
+ #### PowerShell 路径配置
74
+
75
+ 在某些受限环境(如沙箱环境)中,系统 PATH 可能不包含 PowerShell 路径。winterm-mcp 会自动探测以下位置:
76
+
77
+ 1. `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`
78
+ 2. `C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe`
79
+ 3. `C:\Program Files\PowerShell\7\pwsh.exe`(PowerShell Core)
80
+ 4. `C:\Program Files (x86)\PowerShell\7\pwsh.exe`
81
+
82
+ 如果 PowerShell 安装在非标准位置,可通过环境变量指定:
83
+
84
+ **方式一:系统环境变量**
85
+
86
+ ```bash
87
+ # Windows CMD
88
+ set WINTERM_POWERSHELL_PATH=D:\CustomPath\powershell.exe
89
+
90
+ # Windows PowerShell
91
+ $env:WINTERM_POWERSHELL_PATH = "D:\CustomPath\powershell.exe"
92
+ ```
93
+
94
+ **方式二:MCP 配置**
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "winterm": {
100
+ "command": "uvx",
101
+ "args": ["winterm-mcp"],
102
+ "env": {
103
+ "WINTERM_POWERSHELL_PATH": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
104
+ }
63
105
  }
64
106
  }
65
107
  }
@@ -1,7 +1,7 @@
1
1
  # winterm-mcp
2
2
 
3
- **更新日期**: 2026-01-16
4
- **版本**: 0.1.1
3
+ **更新日期**: 2026-01-16
4
+ **版本**: 0.1.4
5
5
 
6
6
  Windows Terminal MCP Service - 专门支持 Windows 终端的异步命令执行工具。
7
7
 
@@ -31,8 +31,50 @@ pip install -e .
31
31
  ```json
32
32
  {
33
33
  "mcpServers": {
34
- "winterm-mcp": {
35
- "command": "winterm-mcp"
34
+ "winterm": {
35
+ "command": "uvx",
36
+ "args": [
37
+ "winterm-mcp"
38
+ ]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### 环境配置
45
+
46
+ #### PowerShell 路径配置
47
+
48
+ 在某些受限环境(如沙箱环境)中,系统 PATH 可能不包含 PowerShell 路径。winterm-mcp 会自动探测以下位置:
49
+
50
+ 1. `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`
51
+ 2. `C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe`
52
+ 3. `C:\Program Files\PowerShell\7\pwsh.exe`(PowerShell Core)
53
+ 4. `C:\Program Files (x86)\PowerShell\7\pwsh.exe`
54
+
55
+ 如果 PowerShell 安装在非标准位置,可通过环境变量指定:
56
+
57
+ **方式一:系统环境变量**
58
+
59
+ ```bash
60
+ # Windows CMD
61
+ set WINTERM_POWERSHELL_PATH=D:\CustomPath\powershell.exe
62
+
63
+ # Windows PowerShell
64
+ $env:WINTERM_POWERSHELL_PATH = "D:\CustomPath\powershell.exe"
65
+ ```
66
+
67
+ **方式二:MCP 配置**
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "winterm": {
73
+ "command": "uvx",
74
+ "args": ["winterm-mcp"],
75
+ "env": {
76
+ "WINTERM_POWERSHELL_PATH": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
77
+ }
36
78
  }
37
79
  }
38
80
  }
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "winterm-mcp"
7
- version = "0.1.3"
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"}
@@ -47,3 +47,9 @@ include = ["winterm_mcp*"]
47
47
 
48
48
  [project.entry-points."mcp.servers"]
49
49
  winterm-mcp = "winterm_mcp.__main__:main"
50
+
51
+ [tool.black]
52
+ line-length = 88
53
+
54
+ [tool.flake8]
55
+ max-line-length = 88
@@ -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[
@@ -54,8 +54,7 @@ def init_service(service: RunCmdService) -> None:
54
54
  def _svc() -> RunCmdService:
55
55
  if _service is None:
56
56
  raise RuntimeError(
57
- "Service not initialized. "
58
- "Call init_service() before running the server."
57
+ "Service not initialized. " "Call init_service() before running the server."
59
58
  )
60
59
  return _service
61
60
 
@@ -93,9 +92,7 @@ def run_command(
93
92
  包含token和状态信息的字典
94
93
  """
95
94
  try:
96
- token = _svc().run_command(
97
- command, shell_type, timeout, working_directory
98
- )
95
+ token = _svc().run_command(command, shell_type, timeout, working_directory)
99
96
  return {"token": token, "status": "pending", "message": "submitted"}
100
97
  except Exception as e:
101
98
  return {"error": str(e)}
@@ -127,3 +124,50 @@ def query_command_status(token: str) -> Dict[str, Any]:
127
124
  return result
128
125
  except Exception as e:
129
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__}
@@ -0,0 +1,404 @@
1
+ """
2
+ winterm服务模块 - 异步执行Windows终端命令服务
3
+ """
4
+
5
+ import subprocess
6
+ import threading
7
+ import uuid
8
+ import time
9
+ import os
10
+ import shutil
11
+ import logging
12
+ from datetime import datetime
13
+ from typing import Dict, Optional, Any, List
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
+
70
+
71
+ # PowerShell 可执行文件的标准路径(按优先级排序)
72
+ POWERSHELL_PATHS: List[str] = [
73
+ r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe",
74
+ r"C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe",
75
+ ]
76
+
77
+ # PowerShell Core (pwsh) 的常见路径
78
+ PWSH_PATHS: List[str] = [
79
+ r"C:\Program Files\PowerShell\7\pwsh.exe",
80
+ r"C:\Program Files (x86)\PowerShell\7\pwsh.exe",
81
+ ]
82
+
83
+ # 环境变量名称
84
+ ENV_POWERSHELL_PATH = "WINTERM_POWERSHELL_PATH"
85
+
86
+
87
+ def _find_powershell() -> str:
88
+ """
89
+ 查找可用的 PowerShell 可执行文件路径
90
+
91
+ 查找顺序:
92
+ 1. 环境变量 WINTERM_POWERSHELL_PATH(用户自定义)
93
+ 2. Windows PowerShell 标准路径
94
+ 3. PowerShell Core 标准路径
95
+ 4. PATH 环境变量中的 powershell/pwsh(兼容正常环境)
96
+
97
+ Returns:
98
+ PowerShell 可执行文件的绝对路径
99
+
100
+ Raises:
101
+ FileNotFoundError: 如果找不到 PowerShell
102
+ """
103
+ logger.debug("Starting PowerShell path discovery...")
104
+
105
+ # 1. 检查用户配置的环境变量
106
+ custom_path = os.environ.get(ENV_POWERSHELL_PATH)
107
+ if custom_path:
108
+ logger.debug(f"Found env var {ENV_POWERSHELL_PATH}={custom_path}")
109
+ if os.path.isfile(custom_path):
110
+ logger.info(f"Using custom PowerShell path: {custom_path}")
111
+ return custom_path
112
+ else:
113
+ logger.warning(
114
+ f"Custom PowerShell path not found: {custom_path}, "
115
+ "falling back to standard paths"
116
+ )
117
+
118
+ # 2. 检查 Windows PowerShell 标准路径
119
+ for path in POWERSHELL_PATHS:
120
+ logger.debug(f"Checking standard path: {path}")
121
+ if os.path.isfile(path):
122
+ logger.info(f"Found Windows PowerShell: {path}")
123
+ return path
124
+
125
+ # 3. 检查 PowerShell Core 标准路径
126
+ for path in PWSH_PATHS:
127
+ logger.debug(f"Checking PowerShell Core path: {path}")
128
+ if os.path.isfile(path):
129
+ logger.info(f"Found PowerShell Core: {path}")
130
+ return path
131
+
132
+ # 4. 尝试 PATH 环境变量(兼容正常环境)
133
+ logger.debug("Checking PATH environment variable...")
134
+ ps_path = shutil.which("powershell")
135
+ if ps_path:
136
+ logger.info(f"Found PowerShell in PATH: {ps_path}")
137
+ return ps_path
138
+
139
+ pwsh_path = shutil.which("pwsh")
140
+ if pwsh_path:
141
+ logger.info(f"Found pwsh in PATH: {pwsh_path}")
142
+ return pwsh_path
143
+
144
+ # 所有方法都失败
145
+ checked_paths = POWERSHELL_PATHS + PWSH_PATHS
146
+ error_msg = (
147
+ f"PowerShell not found. "
148
+ f"Set {ENV_POWERSHELL_PATH} environment variable or "
149
+ f"ensure PowerShell is installed. "
150
+ f"Checked paths: {', '.join(checked_paths)}"
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__
164
+
165
+
166
+ class RunCmdService:
167
+ """
168
+ 异步命令执行服务类,管理所有异步命令的执行和状态
169
+ """
170
+
171
+ def __init__(self):
172
+ self.commands: Dict[str, Dict[str, Any]] = {}
173
+ self.lock = threading.Lock()
174
+ self._powershell_path: Optional[str] = None
175
+
176
+ def _get_powershell_path(self) -> str:
177
+ """
178
+ 获取 PowerShell 可执行文件路径(带缓存)
179
+
180
+ 首次调用时查找并缓存路径,后续调用直接返回缓存值。
181
+
182
+ Returns:
183
+ PowerShell 可执行文件的绝对路径
184
+
185
+ Raises:
186
+ FileNotFoundError: 如果找不到 PowerShell
187
+ """
188
+ if self._powershell_path is None:
189
+ self._powershell_path = _find_powershell()
190
+ logger.debug(f"PowerShell path cached: {self._powershell_path}")
191
+ return self._powershell_path
192
+
193
+ def run_command(
194
+ self,
195
+ command: str,
196
+ shell_type: str = "powershell",
197
+ timeout: int = 30,
198
+ working_directory: Optional[str] = None,
199
+ ) -> str:
200
+ """
201
+ 异步运行命令
202
+
203
+ Args:
204
+ command: 要执行的命令
205
+ shell_type: Shell 类型 (powershell 或 cmd)
206
+ timeout: 超时时间(秒)
207
+ working_directory: 工作目录
208
+
209
+ Returns:
210
+ 命令执行的token
211
+ """
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 ''}")
219
+
220
+ cmd_info = {
221
+ "token": token,
222
+ "command": command,
223
+ "shell_type": shell_type,
224
+ "status": "pending",
225
+ "start_time": datetime.now(),
226
+ "timeout": timeout,
227
+ "working_directory": working_directory,
228
+ "stdout": "",
229
+ "stderr": "",
230
+ "exit_code": None,
231
+ "execution_time": None,
232
+ "timeout_occurred": False,
233
+ }
234
+
235
+ with self.lock:
236
+ self.commands[token] = cmd_info
237
+
238
+ thread = threading.Thread(
239
+ target=self._execute_command,
240
+ args=(token, command, shell_type, timeout, working_directory),
241
+ )
242
+ thread.daemon = True
243
+ thread.start()
244
+
245
+ return token
246
+
247
+ def _execute_command(
248
+ self,
249
+ token: str,
250
+ command: str,
251
+ shell_type: str,
252
+ timeout: int,
253
+ working_directory: Optional[str],
254
+ ):
255
+ """
256
+ 在单独线程中执行命令
257
+ """
258
+ try:
259
+ start_time = time.time()
260
+ logger.debug(f"[{token}] Starting command execution...")
261
+
262
+ with self.lock:
263
+ if token in self.commands:
264
+ self.commands[token]["status"] = "running"
265
+
266
+ encoding = "gbk"
267
+
268
+ if shell_type == "powershell":
269
+ # 使用绝对路径调用 PowerShell,避免 PATH 环境变量限制
270
+ ps_path = self._get_powershell_path()
271
+ logger.info(f"[{token}] Using PowerShell: {ps_path}")
272
+ cmd_args = [
273
+ ps_path,
274
+ "-NoProfile",
275
+ "-NoLogo",
276
+ "-NonInteractive",
277
+ "-ExecutionPolicy",
278
+ "Bypass",
279
+ "-Command",
280
+ command,
281
+ ]
282
+ else:
283
+ logger.debug(f"[{token}] Using cmd.exe")
284
+ cmd_args = ["cmd", "/c", command]
285
+
286
+ logger.debug(f"[{token}] Executing: {cmd_args}")
287
+
288
+ result = subprocess.run(
289
+ cmd_args,
290
+ capture_output=True,
291
+ text=True,
292
+ timeout=timeout,
293
+ cwd=working_directory,
294
+ encoding=encoding,
295
+ stdin=subprocess.DEVNULL, # 防止等待输入导致挂起
296
+ )
297
+
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)'}")
306
+
307
+ with self.lock:
308
+ if token in self.commands:
309
+ self.commands[token].update(
310
+ {
311
+ "status": "completed",
312
+ "stdout": result.stdout,
313
+ "stderr": result.stderr,
314
+ "exit_code": result.returncode,
315
+ "execution_time": execution_time,
316
+ }
317
+ )
318
+
319
+ except FileNotFoundError as e:
320
+ execution_time = time.time() - start_time
321
+ logger.error(f"[{token}] PowerShell not found: {e}")
322
+ with self.lock:
323
+ if token in self.commands:
324
+ self.commands[token].update(
325
+ {
326
+ "status": "completed",
327
+ "stdout": "",
328
+ "stderr": f"PowerShell not found: {e}",
329
+ "exit_code": -2,
330
+ "execution_time": execution_time,
331
+ "timeout_occurred": False,
332
+ }
333
+ )
334
+ except subprocess.TimeoutExpired:
335
+ execution_time = time.time() - start_time
336
+ logger.warning(f"[{token}] Command timed out after {timeout}s")
337
+ with self.lock:
338
+ if token in self.commands:
339
+ self.commands[token].update(
340
+ {
341
+ "status": "completed",
342
+ "stdout": "",
343
+ "stderr": (
344
+ f"Command timed out after {timeout} seconds"
345
+ ),
346
+ "exit_code": -1,
347
+ "execution_time": execution_time,
348
+ "timeout_occurred": True,
349
+ }
350
+ )
351
+ except Exception as e:
352
+ execution_time = time.time() - start_time
353
+ logger.error(f"[{token}] Command failed with exception: {e}")
354
+ with self.lock:
355
+ if token in self.commands:
356
+ self.commands[token].update(
357
+ {
358
+ "status": "completed",
359
+ "stdout": "",
360
+ "stderr": str(e),
361
+ "exit_code": -1,
362
+ "execution_time": execution_time,
363
+ "timeout_occurred": False,
364
+ }
365
+ )
366
+
367
+ def query_command_status(self, token: str) -> Dict[str, Any]:
368
+ """
369
+ 查询命令执行状态
370
+
371
+ Args:
372
+ token: 命令的token
373
+
374
+ Returns:
375
+ 包含命令状态的字典
376
+ """
377
+ logger.debug(f"Querying status for token: {token}")
378
+
379
+ with self.lock:
380
+ if token not in self.commands:
381
+ logger.warning(f"Token not found: {token}")
382
+ return {
383
+ "token": token,
384
+ "status": "not_found",
385
+ "message": "Token not found",
386
+ }
387
+
388
+ cmd_info = self.commands[token].copy()
389
+ logger.debug(f"[{token}] Status: {cmd_info['status']}")
390
+
391
+ if cmd_info["status"] == "running":
392
+ return {"token": cmd_info["token"], "status": "running"}
393
+ elif cmd_info["status"] in ["completed", "pending"]:
394
+ return {
395
+ "token": cmd_info["token"],
396
+ "status": cmd_info["status"],
397
+ "exit_code": cmd_info["exit_code"],
398
+ "stdout": cmd_info["stdout"],
399
+ "stderr": cmd_info["stderr"],
400
+ "execution_time": cmd_info["execution_time"],
401
+ "timeout_occurred": cmd_info["timeout_occurred"],
402
+ }
403
+ else:
404
+ return cmd_info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winterm-mcp
3
- Version: 0.1.3
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
@@ -27,8 +27,8 @@ Dynamic: license-file
27
27
 
28
28
  # winterm-mcp
29
29
 
30
- **更新日期**: 2026-01-16
31
- **版本**: 0.1.1
30
+ **更新日期**: 2026-01-16
31
+ **版本**: 0.1.4
32
32
 
33
33
  Windows Terminal MCP Service - 专门支持 Windows 终端的异步命令执行工具。
34
34
 
@@ -58,8 +58,50 @@ pip install -e .
58
58
  ```json
59
59
  {
60
60
  "mcpServers": {
61
- "winterm-mcp": {
62
- "command": "winterm-mcp"
61
+ "winterm": {
62
+ "command": "uvx",
63
+ "args": [
64
+ "winterm-mcp"
65
+ ]
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### 环境配置
72
+
73
+ #### PowerShell 路径配置
74
+
75
+ 在某些受限环境(如沙箱环境)中,系统 PATH 可能不包含 PowerShell 路径。winterm-mcp 会自动探测以下位置:
76
+
77
+ 1. `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`
78
+ 2. `C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe`
79
+ 3. `C:\Program Files\PowerShell\7\pwsh.exe`(PowerShell Core)
80
+ 4. `C:\Program Files (x86)\PowerShell\7\pwsh.exe`
81
+
82
+ 如果 PowerShell 安装在非标准位置,可通过环境变量指定:
83
+
84
+ **方式一:系统环境变量**
85
+
86
+ ```bash
87
+ # Windows CMD
88
+ set WINTERM_POWERSHELL_PATH=D:\CustomPath\powershell.exe
89
+
90
+ # Windows PowerShell
91
+ $env:WINTERM_POWERSHELL_PATH = "D:\CustomPath\powershell.exe"
92
+ ```
93
+
94
+ **方式二:MCP 配置**
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "winterm": {
100
+ "command": "uvx",
101
+ "args": ["winterm-mcp"],
102
+ "env": {
103
+ "WINTERM_POWERSHELL_PATH": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
104
+ }
63
105
  }
64
106
  }
65
107
  }
@@ -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()
@@ -1,192 +0,0 @@
1
- """
2
- winterm服务模块 - 异步执行Windows终端命令服务
3
- """
4
-
5
- import subprocess
6
- import threading
7
- import uuid
8
- import time
9
- from datetime import datetime
10
- from typing import Dict, Optional, Any
11
-
12
-
13
- class RunCmdService:
14
- """
15
- 异步命令执行服务类,管理所有异步命令的执行和状态
16
- """
17
-
18
- def __init__(self):
19
- self.commands: Dict[str, Dict[str, Any]] = {}
20
- self.lock = threading.Lock()
21
-
22
- def run_command(
23
- self,
24
- command: str,
25
- shell_type: str = "powershell",
26
- timeout: int = 30,
27
- working_directory: Optional[str] = None,
28
- ) -> str:
29
- """
30
- 异步运行命令
31
-
32
- Args:
33
- command: 要执行的命令
34
- shell_type: Shell 类型 (powershell 或 cmd)
35
- timeout: 超时时间(秒)
36
- working_directory: 工作目录
37
-
38
- Returns:
39
- 命令执行的token
40
- """
41
- token = str(uuid.uuid4())
42
-
43
- cmd_info = {
44
- "token": token,
45
- "command": command,
46
- "shell_type": shell_type,
47
- "status": "pending",
48
- "start_time": datetime.now(),
49
- "timeout": timeout,
50
- "working_directory": working_directory,
51
- "stdout": "",
52
- "stderr": "",
53
- "exit_code": None,
54
- "execution_time": None,
55
- "timeout_occurred": False,
56
- }
57
-
58
- with self.lock:
59
- self.commands[token] = cmd_info
60
-
61
- thread = threading.Thread(
62
- target=self._execute_command,
63
- args=(token, command, shell_type, timeout, working_directory),
64
- )
65
- thread.daemon = True
66
- thread.start()
67
-
68
- return token
69
-
70
- def _execute_command(
71
- self,
72
- token: str,
73
- command: str,
74
- shell_type: str,
75
- timeout: int,
76
- working_directory: Optional[str],
77
- ):
78
- """
79
- 在单独线程中执行命令
80
- """
81
- try:
82
- start_time = time.time()
83
-
84
- with self.lock:
85
- if token in self.commands:
86
- self.commands[token]["status"] = "running"
87
-
88
- encoding = "gbk"
89
-
90
- if shell_type == "powershell":
91
- # 添加 -ExecutionPolicy Bypass 避免执行策略阻塞
92
- # 添加 -NoLogo 减少启动输出
93
- cmd_args = [
94
- "powershell",
95
- "-NoProfile",
96
- "-NoLogo",
97
- "-NonInteractive",
98
- "-ExecutionPolicy", "Bypass",
99
- "-Command", command
100
- ]
101
- else:
102
- cmd_args = ["cmd", "/c", command]
103
-
104
- result = subprocess.run(
105
- cmd_args,
106
- capture_output=True,
107
- text=True,
108
- timeout=timeout,
109
- cwd=working_directory,
110
- encoding=encoding,
111
- stdin=subprocess.DEVNULL, # 防止等待输入导致挂起
112
- )
113
-
114
- execution_time = time.time() - start_time
115
-
116
- with self.lock:
117
- if token in self.commands:
118
- self.commands[token].update(
119
- {
120
- "status": "completed",
121
- "stdout": result.stdout,
122
- "stderr": result.stderr,
123
- "exit_code": result.returncode,
124
- "execution_time": execution_time,
125
- }
126
- )
127
-
128
- except subprocess.TimeoutExpired:
129
- execution_time = time.time() - start_time
130
- with self.lock:
131
- if token in self.commands:
132
- self.commands[token].update(
133
- {
134
- "status": "completed",
135
- "stdout": "",
136
- "stderr": (
137
- f"Command timed out after {timeout} seconds"
138
- ),
139
- "exit_code": -1,
140
- "execution_time": execution_time,
141
- "timeout_occurred": True,
142
- }
143
- )
144
- except Exception as e:
145
- execution_time = time.time() - start_time
146
- with self.lock:
147
- if token in self.commands:
148
- self.commands[token].update(
149
- {
150
- "status": "completed",
151
- "stdout": "",
152
- "stderr": str(e),
153
- "exit_code": -1,
154
- "execution_time": execution_time,
155
- "timeout_occurred": False,
156
- }
157
- )
158
-
159
- def query_command_status(self, token: str) -> Dict[str, Any]:
160
- """
161
- 查询命令执行状态
162
-
163
- Args:
164
- token: 命令的token
165
-
166
- Returns:
167
- 包含命令状态的字典
168
- """
169
- with self.lock:
170
- if token not in self.commands:
171
- return {
172
- "token": token,
173
- "status": "not_found",
174
- "message": "Token not found",
175
- }
176
-
177
- cmd_info = self.commands[token].copy()
178
-
179
- if cmd_info["status"] == "running":
180
- return {"token": cmd_info["token"], "status": "running"}
181
- elif cmd_info["status"] in ["completed", "pending"]:
182
- return {
183
- "token": cmd_info["token"],
184
- "status": cmd_info["status"],
185
- "exit_code": cmd_info["exit_code"],
186
- "stdout": cmd_info["stdout"],
187
- "stderr": cmd_info["stderr"],
188
- "execution_time": cmd_info["execution_time"],
189
- "timeout_occurred": cmd_info["timeout_occurred"],
190
- }
191
- else:
192
- return cmd_info
File without changes
File without changes