winterm-mcp 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl

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.
winterm_mcp/__init__.py CHANGED
@@ -1,9 +1,32 @@
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"]
1
+ """
2
+ winterm-mcp - Windows Terminal MCP Service
3
+ """
4
+
5
+ from .service import __version__, get_version, setup_logging, CommandService
6
+ from .models import CommandInfo, QueryStatusResponse, VersionInfo, RunCommandParams
7
+ from .store import CommandStore
8
+ from .utils import find_powershell, find_cmd, resolve_executable_path, strip_ansi_codes
9
+ from .constants import NAME, VERSION, ENV_POWERSHELL_PATH, ENV_CMD_PATH, ENV_PYTHON_PATH
10
+
11
+ __author__ = "winterm-mcp contributors"
12
+
13
+ __all__ = [
14
+ "__version__",
15
+ "get_version",
16
+ "setup_logging",
17
+ "CommandService",
18
+ "CommandInfo",
19
+ "QueryStatusResponse",
20
+ "VersionInfo",
21
+ "RunCommandParams",
22
+ "CommandStore",
23
+ "find_powershell",
24
+ "find_cmd",
25
+ "resolve_executable_path",
26
+ "strip_ansi_codes",
27
+ "NAME",
28
+ "VERSION",
29
+ "ENV_POWERSHELL_PATH",
30
+ "ENV_CMD_PATH",
31
+ "ENV_PYTHON_PATH",
32
+ ]
winterm_mcp/__main__.py CHANGED
@@ -3,7 +3,7 @@ winterm-mcp 主入口
3
3
  """
4
4
 
5
5
  from .server import app, init_service
6
- from .service import RunCmdService, setup_logging, __version__
6
+ from .service import CommandService, setup_logging, __version__
7
7
  import logging
8
8
  import os
9
9
  import tempfile
@@ -13,27 +13,28 @@ def main():
13
13
  """
14
14
  主函数,启动 MCP 服务器
15
15
  """
16
- # 初始化日志
17
16
  setup_logging(logging.INFO)
18
-
17
+
19
18
  logger = logging.getLogger("winterm-mcp")
20
-
21
- # 获取日志文件路径并记录
19
+
22
20
  log_file = os.environ.get("WINTERM_LOG_FILE") or os.path.join(
23
21
  tempfile.gettempdir(), "winterm-mcp.log"
24
22
  )
25
-
23
+
26
24
  logger.info("=" * 60)
27
25
  logger.info(f"winterm-mcp v{__version__} starting...")
28
26
  logger.info(f"Log file: {log_file}")
29
27
  logger.info(f"Temp dir: {tempfile.gettempdir()}")
30
28
  logger.info(f"Working dir: {os.getcwd()}")
31
- logger.info(f"WINTERM_POWERSHELL_PATH: {os.environ.get('WINTERM_POWERSHELL_PATH', '(not set)')}")
29
+ logger.info(
30
+ f"WINTERM_POWERSHELL_PATH: "
31
+ f"{os.environ.get('WINTERM_POWERSHELL_PATH', '(not set)')}"
32
+ )
32
33
  logger.info("=" * 60)
33
-
34
- service = RunCmdService()
34
+
35
+ service = CommandService()
35
36
  init_service(service)
36
-
37
+
37
38
  logger.info("Service initialized, starting MCP server...")
38
39
  app.run()
39
40
 
@@ -0,0 +1,32 @@
1
+ """
2
+ 常量定义 - winterm-mcp
3
+ """
4
+
5
+ NAME = "winterm-mcp"
6
+ VERSION = "0.1.7"
7
+
8
+ ENV_POWERSHELL_PATH = "WINTERM_POWERSHELL_PATH"
9
+ ENV_CMD_PATH = "WINTERM_CMD_PATH"
10
+ ENV_PYTHON_PATH = "WINTERM_PYTHON_PATH"
11
+ ENV_LOG_LEVEL = "WINTERM_LOG_LEVEL"
12
+
13
+ POWERSHELL_PATHS = [
14
+ r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe",
15
+ r"C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe",
16
+ ]
17
+
18
+ PWSH_PATHS = [
19
+ r"C:\Program Files\PowerShell\7\pwsh.exe",
20
+ r"C:\Program Files (x86)\PowerShell\7\pwsh.exe",
21
+ ]
22
+
23
+ CMD_PATHS = [
24
+ r"C:\Windows\System32\cmd.exe",
25
+ ]
26
+
27
+ PTY_COLS = 80
28
+ PTY_ROWS = 30
29
+
30
+ MIN_TIMEOUT = 1
31
+ MAX_TIMEOUT = 3600
32
+ DEFAULT_TIMEOUT = 30
winterm_mcp/models.py ADDED
@@ -0,0 +1,73 @@
1
+ """
2
+ 数据模型定义 - winterm-mcp
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+ from typing import Literal, Any
8
+
9
+
10
+ @dataclass
11
+ class CommandInfo:
12
+ """
13
+ 命令信息数据类
14
+ """
15
+ token: str
16
+ executable: str
17
+ args: list[str]
18
+ command: str
19
+ shell_type: Literal["powershell", "cmd", "executable"]
20
+ status: Literal["pending", "running", "completed", "not_found", "terminated"]
21
+ start_time: datetime
22
+ timeout: int
23
+ working_directory: str | None
24
+ stdout: str
25
+ stderr: str
26
+ exit_code: int | None
27
+ execution_time: int | None
28
+ timeout_occurred: bool
29
+ pty_process: Any | None
30
+ enable_streaming: bool
31
+ last_output_timestamp: int = field(default_factory=lambda: int(datetime.now().timestamp() * 1000))
32
+
33
+
34
+ @dataclass
35
+ class QueryStatusResponse:
36
+ """
37
+ 状态查询响应数据类
38
+ """
39
+ token: str
40
+ status: str
41
+ exit_code: int | None = None
42
+ stdout: str | None = None
43
+ stderr: str | None = None
44
+ execution_time: int | None = None
45
+ timeout_occurred: bool | None = None
46
+ message: str | None = None
47
+
48
+
49
+ @dataclass
50
+ class VersionInfo:
51
+ """
52
+ 版本信息数据类
53
+ """
54
+ version: str
55
+ service_status: str
56
+ python_version: str
57
+ platform: str
58
+ arch: str
59
+ env: dict[str, str | None]
60
+
61
+
62
+ @dataclass
63
+ class RunCommandParams:
64
+ """
65
+ 执行命令参数数据类
66
+ """
67
+ command: str
68
+ executable: str | None = None
69
+ args: list[str] | None = None
70
+ shell_type: Literal["powershell", "cmd", "executable"] = "executable"
71
+ timeout: int = 30
72
+ working_directory: str | None = None
73
+ enable_streaming: bool = False
winterm_mcp/server.py CHANGED
@@ -1,173 +1,285 @@
1
- from __future__ import annotations
2
-
3
- from typing import Annotated, Optional, Dict, Any
4
- from mcp.server.fastmcp import FastMCP
5
- from .service import RunCmdService, get_version, setup_logging, __version__
6
- from pydantic import Field
7
-
8
- CommandStr = Annotated[
9
- str,
10
- Field(
11
- description="要执行的命令字符串",
12
- min_length=1,
13
- max_length=1000,
14
- ),
15
- ]
16
-
17
- ShellTypeStr = Annotated[
18
- str,
19
- Field(
20
- description="Shell 类型 (powershell cmd),默认 powershell",
21
- pattern="^(powershell|cmd)$",
22
- ),
23
- ]
24
-
25
- TimeoutInt = Annotated[
26
- Optional[int],
27
- Field(
28
- description="超时秒数 (1-3600),默认 30 秒",
29
- ge=1,
30
- le=3600,
31
- default=30,
32
- ),
33
- ]
34
-
35
- WorkingDirectoryStr = Annotated[
36
- Optional[str],
37
- Field(
38
- description="工作目录(可选,默认为当前目录)",
39
- default=None,
40
- max_length=1000,
41
- ),
42
- ]
43
-
44
- app = FastMCP("winterm-mcp")
45
-
46
- _service: Optional[RunCmdService] = None
47
-
48
-
49
- def init_service(service: RunCmdService) -> None:
50
- global _service
51
- _service = service
52
-
53
-
54
- def _svc() -> RunCmdService:
55
- if _service is None:
56
- raise RuntimeError(
57
- "Service not initialized. " "Call init_service() before running the server."
58
- )
59
- return _service
60
-
61
-
62
- @app.tool(
63
- name="run_command",
64
- description=(
65
- "异步执行Windows终端命令,立即返回 token。"
66
- "命令将在后台执行,可通过 query_command_status 查询结果。"
67
- ),
68
- annotations={
69
- "title": "异步命令执行器",
70
- "readOnlyHint": False,
71
- "destructiveHint": True,
72
- "idempotentHint": False,
73
- "openWorldHint": True,
74
- },
75
- )
76
- def run_command(
77
- command: CommandStr,
78
- shell_type: ShellTypeStr = "powershell",
79
- timeout: TimeoutInt = 30,
80
- working_directory: WorkingDirectoryStr = None,
81
- ) -> Dict[str, Any]:
82
- """
83
- 异步执行Windows终端命令
84
-
85
- Args:
86
- command: 要执行的命令
87
- shell_type: Shell 类型 (powershell 或 cmd),默认 powershell
88
- timeout: 超时秒数 (1-3600),默认 30 秒
89
- working_directory: 工作目录(可选,默认为当前目录)
90
-
91
- Returns:
92
- 包含token和状态信息的字典
93
- """
94
- try:
95
- token = _svc().run_command(command, shell_type, timeout, working_directory)
96
- return {"token": token, "status": "pending", "message": "submitted"}
97
- except Exception as e:
98
- return {"error": str(e)}
99
-
100
-
101
- @app.tool(
102
- name="query_command_status",
103
- description=("查询命令执行状态和结果。" "返回命令的当前状态、退出码、输出等信息。"),
104
- annotations={
105
- "title": "命令状态查询器",
106
- "readOnlyHint": True,
107
- "destructiveHint": False,
108
- "idempotentHint": True,
109
- "openWorldHint": False,
110
- },
111
- )
112
- def query_command_status(token: str) -> Dict[str, Any]:
113
- """
114
- 查询命令执行状态和结果
115
-
116
- Args:
117
- token: 任务 token (GUID 字符串)
118
-
119
- Returns:
120
- 包含命令状态和结果的字典
121
- """
122
- try:
123
- result = _svc().query_command_status(token)
124
- return result
125
- except Exception as e:
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__}
1
+ from __future__ import annotations
2
+
3
+ from typing import Annotated, Optional, Dict, Any
4
+ from mcp.server.fastmcp import FastMCP
5
+ from .service import CommandService, get_version, __version__
6
+ from pydantic import Field
7
+
8
+ CommandStr = Annotated[
9
+ str,
10
+ Field(
11
+ description="要执行的命令字符串",
12
+ min_length=1,
13
+ max_length=1000,
14
+ ),
15
+ ]
16
+
17
+ ShellTypeStr = Annotated[
18
+ str,
19
+ Field(
20
+ description="Shell 类型 (powershell, cmd, executable),默认 executable",
21
+ pattern="^(powershell|cmd|executable)$",
22
+ ),
23
+ ]
24
+
25
+ TimeoutInt = Annotated[
26
+ Optional[int],
27
+ Field(
28
+ description="超时秒数 (1-3600),默认 30 秒",
29
+ ge=1,
30
+ le=3600,
31
+ default=30,
32
+ ),
33
+ ]
34
+
35
+ WorkingDirectoryStr = Annotated[
36
+ Optional[str],
37
+ Field(
38
+ description="工作目录(可选,默认为当前目录)",
39
+ default=None,
40
+ max_length=1000,
41
+ ),
42
+ ]
43
+
44
+ ExecutableStr = Annotated[
45
+ Optional[str],
46
+ Field(
47
+ description="可执行文件路径(仅当 shell_type 为 executable 时使用)",
48
+ default=None,
49
+ ),
50
+ ]
51
+
52
+ ArgsList = Annotated[
53
+ Optional[list[str]],
54
+ Field(
55
+ description="可执行文件参数列表(仅当 shell_type executable 时使用)",
56
+ default=None,
57
+ ),
58
+ ]
59
+
60
+ app = FastMCP("winterm-mcp")
61
+
62
+ _service: Optional[CommandService] = None
63
+
64
+
65
+ def init_service(service: CommandService) -> None:
66
+ global _service
67
+ _service = service
68
+
69
+
70
+ def _svc() -> CommandService:
71
+ if _service is None:
72
+ raise RuntimeError(
73
+ "Service not initialized. "
74
+ "Call init_service() before running the server."
75
+ )
76
+ return _service
77
+
78
+
79
+ @app.tool(
80
+ name="run_command",
81
+ description=(
82
+ "异步执行Windows终端命令,立即返回 token。"
83
+ "命令将在后台执行,可通过 query_command_status 查询结果。"
84
+ ),
85
+ annotations={
86
+ "title": "异步命令执行器",
87
+ "readOnlyHint": False,
88
+ "destructiveHint": True,
89
+ "idempotentHint": False,
90
+ "openWorldHint": True,
91
+ },
92
+ )
93
+ def run_command(
94
+ command: CommandStr,
95
+ shell_type: ShellTypeStr = "executable",
96
+ timeout: TimeoutInt = 30,
97
+ working_directory: WorkingDirectoryStr = None,
98
+ executable: ExecutableStr = None,
99
+ args: ArgsList = None,
100
+ enable_streaming: bool = False,
101
+ ) -> Dict[str, Any]:
102
+ """
103
+ 异步执行Windows终端命令
104
+
105
+ Args:
106
+ command: 要执行的命令
107
+ shell_type: Shell 类型 (powershell, cmd, executable),默认 executable
108
+ timeout: 超时秒数 (1-3600),默认 30 秒
109
+ working_directory: 工作目录(可选,默认为当前目录)
110
+ executable: 可执行文件路径(仅当 shell_type 为 executable 时使用)
111
+ args: 可执行文件参数列表(仅当 shell_type 为 executable 时使用)
112
+ enable_streaming: 启用实时流式输出
113
+
114
+ Returns:
115
+ 包含token和状态信息的字典
116
+ """
117
+ try:
118
+ token = _svc().run_command(
119
+ command,
120
+ executable,
121
+ args,
122
+ shell_type,
123
+ timeout,
124
+ working_directory,
125
+ enable_streaming,
126
+ )
127
+ return {"token": token, "status": "pending", "message": "submitted"}
128
+ except Exception as e:
129
+ return {"error": str(e)}
130
+
131
+
132
+ @app.tool(
133
+ name="query_command_status",
134
+ description=("查询命令执行状态和结果。" "返回命令的当前状态、退出码、输出等信息。"),
135
+ annotations={
136
+ "title": "命令状态查询器",
137
+ "readOnlyHint": True,
138
+ "destructiveHint": False,
139
+ "idempotentHint": True,
140
+ "openWorldHint": False,
141
+ },
142
+ )
143
+ def query_command_status(token: str) -> Dict[str, Any]:
144
+ """
145
+ 查询命令执行状态和结果
146
+
147
+ Args:
148
+ token: 任务 token (GUID 字符串)
149
+
150
+ Returns:
151
+ 包含命令状态和结果的字典
152
+ """
153
+ try:
154
+ result = _svc().query_command_status(token)
155
+ return result
156
+ except Exception as e:
157
+ return {"error": str(e)}
158
+
159
+
160
+ @app.tool(
161
+ name="enhanced_query_command_status",
162
+ description=(
163
+ "增强版命令状态查询,支持流式输出。"
164
+ "可以指定时间戳,只返回该时间戳之后的输出。"
165
+ ),
166
+ annotations={
167
+ "title": "增强版命令状态查询器",
168
+ "readOnlyHint": True,
169
+ "destructiveHint": False,
170
+ "idempotentHint": True,
171
+ "openWorldHint": False,
172
+ },
173
+ )
174
+ def enhanced_query_command_status(
175
+ token: str,
176
+ since_timestamp: Optional[int] = None,
177
+ ) -> Dict[str, Any]:
178
+ """
179
+ 增强版命令状态查询,支持流式输出
180
+
181
+ Args:
182
+ token: 任务 token (GUID 字符串)
183
+ since_timestamp: 时间戳(毫秒),只返回此时间戳之后的输出
184
+
185
+ Returns:
186
+ 包含命令状态和结果的字典
187
+ """
188
+ try:
189
+ result = _svc().enhanced_query_command_status(token, since_timestamp)
190
+ return result
191
+ except Exception as e:
192
+ return {"error": str(e)}
193
+
194
+
195
+ @app.tool(
196
+ name="send_command_input",
197
+ description=(
198
+ "向正在运行的命令发送输入。"
199
+ "用于交互式命令,例如需要用户输入的程序。"
200
+ ),
201
+ annotations={
202
+ "title": "命令输入发送器",
203
+ "readOnlyHint": False,
204
+ "destructiveHint": False,
205
+ "idempotentHint": False,
206
+ "openWorldHint": False,
207
+ },
208
+ )
209
+ def send_command_input(
210
+ token: str,
211
+ input: str,
212
+ append_newline: bool = True,
213
+ ) -> Dict[str, Any]:
214
+ """
215
+ 向正在运行的命令发送输入
216
+
217
+ Args:
218
+ token: 任务 token (GUID 字符串)
219
+ input: 要发送的输入内容
220
+ append_newline: 是否在输入后追加换行符,默认 True
221
+
222
+ Returns:
223
+ 包含操作结果的字典
224
+ """
225
+ try:
226
+ result = _svc().send_command_input(token, input, append_newline)
227
+ return result
228
+ except Exception as e:
229
+ return {"error": str(e)}
230
+
231
+
232
+ @app.tool(
233
+ name="terminate_command",
234
+ description=(
235
+ "终止正在运行的命令。"
236
+ "强制停止命令执行,适用于长时间运行的命令。"
237
+ ),
238
+ annotations={
239
+ "title": "命令终止器",
240
+ "readOnlyHint": False,
241
+ "destructiveHint": True,
242
+ "idempotentHint": False,
243
+ "openWorldHint": False,
244
+ },
245
+ )
246
+ def terminate_command(token: str) -> Dict[str, Any]:
247
+ """
248
+ 终止正在运行的命令
249
+
250
+ Args:
251
+ token: 任务 token (GUID 字符串)
252
+
253
+ Returns:
254
+ 包含操作结果的字典
255
+ """
256
+ try:
257
+ result = _svc().terminate_command(token)
258
+ return result
259
+ except Exception as e:
260
+ return {"error": str(e)}
261
+
262
+
263
+ @app.tool(
264
+ name="get_version",
265
+ description="获取 winterm-mcp 服务的版本信息和运行状态。",
266
+ annotations={
267
+ "title": "版本信息",
268
+ "readOnlyHint": True,
269
+ "destructiveHint": False,
270
+ "idempotentHint": True,
271
+ "openWorldHint": False,
272
+ },
273
+ )
274
+ def get_version_tool() -> Dict[str, Any]:
275
+ """
276
+ 获取 winterm-mcp 版本信息
277
+
278
+ Returns:
279
+ 包含版本号和服务状态的字典
280
+ """
281
+ try:
282
+ result = _svc().get_version_info()
283
+ return result
284
+ except Exception as e:
285
+ return {"error": str(e), "version": __version__}