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 +32 -9
- winterm_mcp/__main__.py +11 -10
- winterm_mcp/constants.py +32 -0
- winterm_mcp/models.py +73 -0
- winterm_mcp/server.py +285 -173
- winterm_mcp/service.py +544 -239
- winterm_mcp/store.py +92 -0
- winterm_mcp/utils.py +206 -0
- {winterm_mcp-0.1.5.dist-info → winterm_mcp-0.1.7.dist-info}/METADATA +3 -2
- winterm_mcp-0.1.7.dist-info/RECORD +14 -0
- {winterm_mcp-0.1.5.dist-info → winterm_mcp-0.1.7.dist-info}/WHEEL +1 -1
- winterm_mcp-0.1.5.dist-info/RECORD +0 -10
- {winterm_mcp-0.1.5.dist-info → winterm_mcp-0.1.7.dist-info}/entry_points.txt +0 -0
- {winterm_mcp-0.1.5.dist-info → winterm_mcp-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {winterm_mcp-0.1.5.dist-info → winterm_mcp-0.1.7.dist-info}/top_level.txt +0 -0
winterm_mcp/store.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
命令存储管理 - winterm-mcp
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from .models import CommandInfo
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("winterm-mcp")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CommandStore:
|
|
14
|
+
"""
|
|
15
|
+
命令存储管理类,线程安全
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
self._commands: dict[str, CommandInfo] = {}
|
|
20
|
+
self._lock = threading.Lock()
|
|
21
|
+
|
|
22
|
+
def add_command(self, token: str, info: CommandInfo) -> None:
|
|
23
|
+
"""
|
|
24
|
+
添加命令信息到存储
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
token: 命令令牌
|
|
28
|
+
info: 命令信息对象
|
|
29
|
+
"""
|
|
30
|
+
with self._lock:
|
|
31
|
+
self._commands[token] = info
|
|
32
|
+
logger.debug(f"Added command to store: token={token}")
|
|
33
|
+
|
|
34
|
+
def get_command(self, token: str) -> Optional[CommandInfo]:
|
|
35
|
+
"""
|
|
36
|
+
获取命令信息
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
token: 命令令牌
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
命令信息对象,如果不存在则返回 None
|
|
43
|
+
"""
|
|
44
|
+
with self._lock:
|
|
45
|
+
return self._commands.get(token)
|
|
46
|
+
|
|
47
|
+
def remove_command(self, token: str) -> bool:
|
|
48
|
+
"""
|
|
49
|
+
移除命令信息
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
token: 命令令牌
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
如果命令存在并被移除返回 True,否则返回 False
|
|
56
|
+
"""
|
|
57
|
+
with self._lock:
|
|
58
|
+
if token in self._commands:
|
|
59
|
+
del self._commands[token]
|
|
60
|
+
logger.debug(f"Removed command from store: token={token}")
|
|
61
|
+
return True
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
def get_all_tokens(self) -> list[str]:
|
|
65
|
+
"""
|
|
66
|
+
获取所有命令令牌
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
令牌列表
|
|
70
|
+
"""
|
|
71
|
+
with self._lock:
|
|
72
|
+
return list(self._commands.keys())
|
|
73
|
+
|
|
74
|
+
def update_command(self, token: str, **kwargs) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
更新命令信息
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
token: 命令令牌
|
|
80
|
+
**kwargs: 要更新的字段
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
如果命令存在并被更新返回 True,否则返回 False
|
|
84
|
+
"""
|
|
85
|
+
with self._lock:
|
|
86
|
+
if token in self._commands:
|
|
87
|
+
for key, value in kwargs.items():
|
|
88
|
+
if hasattr(self._commands[token], key):
|
|
89
|
+
setattr(self._commands[token], key, value)
|
|
90
|
+
logger.debug(f"Updated command in store: token={token}, fields={list(kwargs.keys())}")
|
|
91
|
+
return True
|
|
92
|
+
return False
|
winterm_mcp/utils.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
工具函数 - winterm-mcp
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from .constants import (
|
|
11
|
+
ENV_POWERSHELL_PATH,
|
|
12
|
+
ENV_CMD_PATH,
|
|
13
|
+
POWERSHELL_PATHS,
|
|
14
|
+
PWSH_PATHS,
|
|
15
|
+
CMD_PATHS,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("winterm-mcp")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def find_powershell() -> str:
|
|
22
|
+
"""
|
|
23
|
+
查找 PowerShell 可执行文件路径
|
|
24
|
+
|
|
25
|
+
优先级:
|
|
26
|
+
1. WINTERM_POWERSHELL_PATH 环境变量
|
|
27
|
+
2. 标准 PowerShell 路径
|
|
28
|
+
3. PowerShell Core 路径
|
|
29
|
+
4. PATH 环境变量
|
|
30
|
+
5. 抛出 FileNotFoundError
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
PowerShell 可执行文件的绝对路径
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
FileNotFoundError: 如果找不到 PowerShell
|
|
37
|
+
"""
|
|
38
|
+
logger.debug("Starting PowerShell path discovery...")
|
|
39
|
+
|
|
40
|
+
custom_path = os.environ.get(ENV_POWERSHELL_PATH)
|
|
41
|
+
if custom_path:
|
|
42
|
+
logger.debug(f"Found env var {ENV_POWERSHELL_PATH}={custom_path}")
|
|
43
|
+
if os.path.isfile(custom_path):
|
|
44
|
+
logger.info(f"Using custom PowerShell path: {custom_path}")
|
|
45
|
+
return custom_path
|
|
46
|
+
else:
|
|
47
|
+
logger.warning(
|
|
48
|
+
f"Custom PowerShell path not found: {custom_path}, "
|
|
49
|
+
"falling back to standard paths"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
for path in POWERSHELL_PATHS:
|
|
53
|
+
logger.debug(f"Checking standard path: {path}")
|
|
54
|
+
if os.path.isfile(path):
|
|
55
|
+
logger.info(f"Found Windows PowerShell: {path}")
|
|
56
|
+
return path
|
|
57
|
+
|
|
58
|
+
for path in PWSH_PATHS:
|
|
59
|
+
logger.debug(f"Checking PowerShell Core path: {path}")
|
|
60
|
+
if os.path.isfile(path):
|
|
61
|
+
logger.info(f"Found PowerShell Core: {path}")
|
|
62
|
+
return path
|
|
63
|
+
|
|
64
|
+
logger.debug("Checking PATH environment variable...")
|
|
65
|
+
ps_path = shutil.which("powershell")
|
|
66
|
+
if ps_path:
|
|
67
|
+
logger.info(f"Found PowerShell in PATH: {ps_path}")
|
|
68
|
+
return ps_path
|
|
69
|
+
|
|
70
|
+
pwsh_path = shutil.which("pwsh")
|
|
71
|
+
if pwsh_path:
|
|
72
|
+
logger.info(f"Found pwsh in PATH: {pwsh_path}")
|
|
73
|
+
return pwsh_path
|
|
74
|
+
|
|
75
|
+
checked_paths = POWERSHELL_PATHS + PWSH_PATHS
|
|
76
|
+
error_msg = (
|
|
77
|
+
f"PowerShell not found. "
|
|
78
|
+
f"Set {ENV_POWERSHELL_PATH} environment variable or "
|
|
79
|
+
f"ensure PowerShell is installed. "
|
|
80
|
+
f"Checked paths: {', '.join(checked_paths)}"
|
|
81
|
+
)
|
|
82
|
+
logger.error(error_msg)
|
|
83
|
+
raise FileNotFoundError(error_msg)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def find_cmd() -> str:
|
|
87
|
+
"""
|
|
88
|
+
查找 CMD 可执行文件路径
|
|
89
|
+
|
|
90
|
+
优先级:
|
|
91
|
+
1. WINTERM_CMD_PATH 环境变量
|
|
92
|
+
2. 标准 CMD 路径
|
|
93
|
+
3. PATH 环境变量
|
|
94
|
+
4. 抛出 FileNotFoundError
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
CMD 可执行文件的绝对路径
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
FileNotFoundError: 如果找不到 CMD
|
|
101
|
+
"""
|
|
102
|
+
logger.debug("Starting CMD path discovery...")
|
|
103
|
+
|
|
104
|
+
custom_path = os.environ.get(ENV_CMD_PATH)
|
|
105
|
+
if custom_path:
|
|
106
|
+
logger.debug(f"Found env var {ENV_CMD_PATH}={custom_path}")
|
|
107
|
+
if os.path.isfile(custom_path):
|
|
108
|
+
logger.info(f"Using custom CMD path: {custom_path}")
|
|
109
|
+
return custom_path
|
|
110
|
+
else:
|
|
111
|
+
logger.warning(
|
|
112
|
+
f"Custom CMD path not found: {custom_path}, "
|
|
113
|
+
"falling back to standard paths"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
for path in CMD_PATHS:
|
|
117
|
+
logger.debug(f"Checking standard path: {path}")
|
|
118
|
+
if os.path.isfile(path):
|
|
119
|
+
logger.info(f"Found CMD: {path}")
|
|
120
|
+
return path
|
|
121
|
+
|
|
122
|
+
logger.debug("Checking PATH environment variable...")
|
|
123
|
+
cmd_path = shutil.which("cmd")
|
|
124
|
+
if cmd_path:
|
|
125
|
+
logger.info(f"Found CMD in PATH: {cmd_path}")
|
|
126
|
+
return cmd_path
|
|
127
|
+
|
|
128
|
+
error_msg = (
|
|
129
|
+
f"CMD not found. "
|
|
130
|
+
f"Set {ENV_CMD_PATH} environment variable or "
|
|
131
|
+
f"ensure CMD is installed. "
|
|
132
|
+
f"Checked paths: {', '.join(CMD_PATHS)}"
|
|
133
|
+
)
|
|
134
|
+
logger.error(error_msg)
|
|
135
|
+
raise FileNotFoundError(error_msg)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def resolve_executable_path(executable: str) -> str:
|
|
139
|
+
"""
|
|
140
|
+
解析可执行文件路径
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
executable: 可执行文件名称或路径
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
解析后的可执行文件路径
|
|
147
|
+
|
|
148
|
+
规则:
|
|
149
|
+
- 如果是绝对路径,直接返回
|
|
150
|
+
- 检查当前目录
|
|
151
|
+
- 在 PATH 中搜索(支持 .exe, .bat, .cmd, .com, .ps1)
|
|
152
|
+
- 返回原始名称(让系统处理错误)
|
|
153
|
+
"""
|
|
154
|
+
if os.path.isabs(executable):
|
|
155
|
+
logger.debug(f"Executable is absolute path: {executable}")
|
|
156
|
+
return executable
|
|
157
|
+
|
|
158
|
+
if os.path.isfile(executable):
|
|
159
|
+
logger.debug(f"Executable found in current directory: {executable}")
|
|
160
|
+
return os.path.abspath(executable)
|
|
161
|
+
|
|
162
|
+
extensions = [".exe", ".bat", ".cmd", ".com", ".ps1"]
|
|
163
|
+
|
|
164
|
+
for ext in extensions:
|
|
165
|
+
if executable.lower().endswith(ext):
|
|
166
|
+
path = shutil.which(executable)
|
|
167
|
+
if path:
|
|
168
|
+
logger.debug(f"Found executable in PATH: {path}")
|
|
169
|
+
return path
|
|
170
|
+
break
|
|
171
|
+
else:
|
|
172
|
+
for ext in extensions:
|
|
173
|
+
full_path = shutil.which(executable + ext)
|
|
174
|
+
if full_path:
|
|
175
|
+
logger.debug(f"Found executable in PATH: {full_path}")
|
|
176
|
+
return full_path
|
|
177
|
+
|
|
178
|
+
logger.debug(f"Executable not found in PATH, returning original: {executable}")
|
|
179
|
+
return executable
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def strip_ansi_codes(text: str) -> str:
|
|
183
|
+
"""
|
|
184
|
+
移除 ANSI 转义序列
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
text: 包含 ANSI 转义序列的文本
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
清理后的文本
|
|
191
|
+
|
|
192
|
+
支持的转义序列:
|
|
193
|
+
- CSI 序列: \x1b[... 或 \u001b[...
|
|
194
|
+
- OSC 序列: \x1b]...\x07
|
|
195
|
+
- BEL 字符: \x07
|
|
196
|
+
"""
|
|
197
|
+
ansi_escape = re.compile(
|
|
198
|
+
r"""
|
|
199
|
+
\x1b\[[0-9;]*[mGKHfABCDsuJK] | # CSI sequences
|
|
200
|
+
\x1b\][0-9;]*\x07 | # OSC sequences
|
|
201
|
+
\x07 | # BEL character
|
|
202
|
+
\x1b. # Other ESC sequences
|
|
203
|
+
""",
|
|
204
|
+
re.VERBOSE,
|
|
205
|
+
)
|
|
206
|
+
return ansi_escape.sub("", text)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: winterm-mcp
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
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
|
|
@@ -19,6 +19,7 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: fastmcp>=0.1.0
|
|
21
21
|
Requires-Dist: pydantic>=2.0.0
|
|
22
|
+
Requires-Dist: pywinpty>=2.0.0
|
|
22
23
|
Provides-Extra: dev
|
|
23
24
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
24
25
|
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
@@ -28,7 +29,7 @@ Dynamic: license-file
|
|
|
28
29
|
# winterm-mcp
|
|
29
30
|
|
|
30
31
|
**更新日期**: 2026-01-16
|
|
31
|
-
**版本**: 0.1.
|
|
32
|
+
**版本**: 0.1.6
|
|
32
33
|
|
|
33
34
|
Windows Terminal MCP Service - 专门支持 Windows 终端的异步命令执行工具。
|
|
34
35
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
winterm_mcp/__init__.py,sha256=y-6DZGsz4wX5rCZPBH5uQv0S3RYlxt2bB5RyiEe1ts8,859
|
|
2
|
+
winterm_mcp/__main__.py,sha256=jtZWUuc5hQpO5xU_YFak21QXrCM9nKEB2dGScXovJPc,1044
|
|
3
|
+
winterm_mcp/constants.py,sha256=-n1_WlNpAsfn6lp4D3SQW3xFwZsWOFnIp1UIFC3sTGs,647
|
|
4
|
+
winterm_mcp/models.py,sha256=zfzAd7k2a8nTmyM3QWuqPFr2ArELf5SgNyjTeWKcgAQ,1721
|
|
5
|
+
winterm_mcp/server.py,sha256=tz3JT6jpko-tv34rSIRLunLl1z5sZInT9wdmkDM2zNs,7613
|
|
6
|
+
winterm_mcp/service.py,sha256=HO1rPL4UyG2XxdT1rVdrs203tFd6FY-6S2pbhFYEPtg,22370
|
|
7
|
+
winterm_mcp/store.py,sha256=obHzcTP1c7_6r2DzgbUBysx2XpA7Esfl00wLsPSOqS4,2421
|
|
8
|
+
winterm_mcp/utils.py,sha256=uIjNFKP3T3q5ztNtxtquWKFhiWo4gjecOdHl8X3pXxA,5763
|
|
9
|
+
winterm_mcp-0.1.7.dist-info/licenses/LICENSE,sha256=GPZ4VAbf_gxeFMSUHCmNQING30GK0kNduK8EJIO5-gc,1081
|
|
10
|
+
winterm_mcp-0.1.7.dist-info/METADATA,sha256=4apHA0um7BT2WPWcl6mrqLyppveJCpENeQ-ODAxfVcc,5138
|
|
11
|
+
winterm_mcp-0.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
12
|
+
winterm_mcp-0.1.7.dist-info/entry_points.txt,sha256=0OGioH1DKGxuxvSFD1OCRcbLoutfqdEPmJy_j2GhJTA,113
|
|
13
|
+
winterm_mcp-0.1.7.dist-info/top_level.txt,sha256=S7w96DR3MB-CMIuXLnrMOvs5ApqPF-2PsAcfVCphCHw,12
|
|
14
|
+
winterm_mcp-0.1.7.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
winterm_mcp/__init__.py,sha256=SKtzW0lc0ixl5FUdEaJA6J7tryL-JYTvsPG8wUi5Dxk,222
|
|
2
|
-
winterm_mcp/__main__.py,sha256=5K6N5K-ooaAb1X17EmEQUid8zne-A457NAGCT33qGIg,1097
|
|
3
|
-
winterm_mcp/server.py,sha256=wKoCItWQBG4b2cODluWfk8ABjmXlDlFxVyK4tD7NBiY,4541
|
|
4
|
-
winterm_mcp/service.py,sha256=T7nlrKZDTqudScm-eEhDi72JRN82qxU3C9l_rOzMpOw,13213
|
|
5
|
-
winterm_mcp-0.1.5.dist-info/licenses/LICENSE,sha256=GPZ4VAbf_gxeFMSUHCmNQING30GK0kNduK8EJIO5-gc,1081
|
|
6
|
-
winterm_mcp-0.1.5.dist-info/METADATA,sha256=PCRDUAN9ZtgRcxl-9PkH44jDBxwP6IAiHKBBsHyCd5I,5106
|
|
7
|
-
winterm_mcp-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
winterm_mcp-0.1.5.dist-info/entry_points.txt,sha256=0OGioH1DKGxuxvSFD1OCRcbLoutfqdEPmJy_j2GhJTA,113
|
|
9
|
-
winterm_mcp-0.1.5.dist-info/top_level.txt,sha256=S7w96DR3MB-CMIuXLnrMOvs5ApqPF-2PsAcfVCphCHw,12
|
|
10
|
-
winterm_mcp-0.1.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|