everything-mcp 1.0.0__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.
@@ -0,0 +1,253 @@
1
+ """
2
+ Auto-detection and configuration for voidtools Everything.
3
+
4
+ Finds es.exe, detects Everything version/instance, and validates the setup.
5
+ Zero-config by default — discovers installation via PATH, common install
6
+ locations, and the Windows Registry.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import os
13
+ import shutil
14
+ import subprocess
15
+ import sys
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+
19
+ __all__ = ["EverythingConfig"]
20
+
21
+ logger = logging.getLogger("everything_mcp")
22
+
23
+ # ── Search locations for es.exe ───────────────────────────────────────────
24
+
25
+ ES_SEARCH_PATHS: list[str] = [
26
+ os.path.expandvars(r"%LOCALAPPDATA%\Microsoft\WindowsApps"),
27
+ r"C:\Program Files\Everything",
28
+ r"C:\Program Files (x86)\Everything",
29
+ r"C:\Program Files\Everything 1.5a",
30
+ r"C:\Program Files (x86)\Everything 1.5a",
31
+ os.path.expandvars(r"%LOCALAPPDATA%\Everything"),
32
+ os.path.expandvars(r"%USERPROFILE%\Everything"),
33
+ os.path.expandvars(r"%PROGRAMDATA%\Everything"),
34
+ os.path.expandvars(r"%USERPROFILE%\scoop\shims"),
35
+ os.path.expandvars(r"%USERPROFILE%\scoop\apps\everything\current"),
36
+ os.path.expandvars(r"%PROGRAMDATA%\chocolatey\bin"),
37
+ ]
38
+
39
+ # Suppress console window on Windows
40
+ _CREATE_NO_WINDOW: int = getattr(subprocess, "CREATE_NO_WINDOW", 0)
41
+
42
+
43
+ @dataclass
44
+ class EverythingConfig:
45
+ """Configuration for communicating with Everything."""
46
+
47
+ es_path: str = ""
48
+ instance: str = ""
49
+ timeout: int = 30
50
+ max_results_cap: int = 1000
51
+ errors: list[str] = field(default_factory=list)
52
+ warnings: list[str] = field(default_factory=list)
53
+ version_info: str = ""
54
+
55
+ @property
56
+ def is_valid(self) -> bool:
57
+ """True when es.exe was found and Everything is responding."""
58
+ return bool(self.es_path) and len(self.errors) == 0
59
+
60
+ @classmethod
61
+ def auto_detect(cls) -> EverythingConfig:
62
+ """Auto-detect Everything installation and return a ready config.
63
+
64
+ Detection order:
65
+ 1. ``EVERYTHING_ES_PATH`` / ``EVERYTHING_INSTANCE`` env vars
66
+ 2. ``es.exe`` on ``PATH`` (verified via ``-get-everything-version``)
67
+ 3. Common installation directories
68
+ 4. Windows Registry
69
+ 5. Instance auto-detection (default → 1.5a)
70
+ 6. Connectivity test
71
+ """
72
+ config = cls()
73
+
74
+ env_path = os.environ.get("EVERYTHING_ES_PATH", "").strip()
75
+ env_instance = os.environ.get("EVERYTHING_INSTANCE", "").strip()
76
+
77
+ if env_instance:
78
+ config.instance = env_instance
79
+ logger.info("Using instance from EVERYTHING_INSTANCE=%s", env_instance)
80
+
81
+ config.es_path = _find_es_exe(env_path)
82
+
83
+ if not config.es_path:
84
+ config.errors.append(
85
+ "es.exe not found. Install from https://github.com/voidtools/es/releases "
86
+ "or set the EVERYTHING_ES_PATH environment variable. "
87
+ "Everything (https://www.voidtools.com/) must be installed and running."
88
+ )
89
+ return config
90
+
91
+ logger.info("Found es.exe: %s", config.es_path)
92
+
93
+ if not config.instance:
94
+ config.instance = _detect_instance(config.es_path)
95
+ if config.instance:
96
+ logger.info("Auto-detected instance: %s", config.instance)
97
+
98
+ ok, info = _test_connection(config.es_path, config.instance)
99
+ if ok:
100
+ config.version_info = info
101
+ logger.info("Everything connection OK: %s", info)
102
+ else:
103
+ config.errors.append(
104
+ f"Cannot connect to Everything: {info}. "
105
+ "Ensure Everything is running (check your system tray). "
106
+ "If you use Everything 1.5 alpha, try EVERYTHING_INSTANCE=1.5a"
107
+ )
108
+
109
+ return config
110
+
111
+
112
+ # ── Internal helpers ──────────────────────────────────────────────────────
113
+
114
+
115
+ def _find_es_exe(env_override: str = "") -> str:
116
+ """Locate the es.exe executable."""
117
+ if env_override:
118
+ p = Path(env_override)
119
+ if p.is_file() and p.name.lower() == "es.exe":
120
+ if _is_everything_es(str(p)):
121
+ return str(p)
122
+ elif p.is_dir():
123
+ candidate = p / "es.exe"
124
+ if candidate.is_file() and _is_everything_es(str(candidate)):
125
+ return str(candidate)
126
+ logger.warning("EVERYTHING_ES_PATH='%s' not valid, continuing search", env_override)
127
+
128
+ for name in ("es", "es.exe"):
129
+ found = shutil.which(name)
130
+ if found and _is_everything_es(found):
131
+ return found
132
+
133
+ for search_dir in ES_SEARCH_PATHS:
134
+ candidate = Path(search_dir) / "es.exe"
135
+ try:
136
+ if candidate.is_file() and _is_everything_es(str(candidate)):
137
+ return str(candidate)
138
+ except OSError:
139
+ continue
140
+
141
+ return _find_via_registry()
142
+
143
+
144
+ def _is_everything_es(path: str) -> bool:
145
+ """Verify that *path* is voidtools Everything's es.exe."""
146
+ try:
147
+ result = subprocess.run(
148
+ [path, "-get-everything-version"],
149
+ capture_output=True, text=True, timeout=5,
150
+ creationflags=_CREATE_NO_WINDOW,
151
+ )
152
+ output = result.stdout.strip()
153
+ return bool(output) and any(c.isdigit() for c in output)
154
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
155
+ pass
156
+
157
+ try:
158
+ result = subprocess.run(
159
+ [path, "-version"],
160
+ capture_output=True, text=True, timeout=5,
161
+ creationflags=_CREATE_NO_WINDOW,
162
+ )
163
+ return "everything" in result.stdout.lower()
164
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
165
+ return False
166
+
167
+
168
+ def _find_via_registry() -> str:
169
+ """Look up the Everything install path from the Windows Registry."""
170
+ if sys.platform != "win32":
171
+ return ""
172
+ try:
173
+ import winreg
174
+ except ImportError:
175
+ return ""
176
+
177
+ for hive in (winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER):
178
+ for subkey in (
179
+ r"SOFTWARE\voidtools\Everything",
180
+ r"SOFTWARE\WOW6432Node\voidtools\Everything",
181
+ ):
182
+ try:
183
+ with winreg.OpenKey(hive, subkey) as key:
184
+ install_path, _ = winreg.QueryValueEx(key, "InstallPath")
185
+ candidate = Path(install_path) / "es.exe"
186
+ if candidate.is_file() and _is_everything_es(str(candidate)):
187
+ return str(candidate)
188
+ except (FileNotFoundError, OSError):
189
+ continue
190
+
191
+ return ""
192
+
193
+
194
+ def _detect_instance(es_path: str) -> str:
195
+ """Detect which Everything instance is running (default vs 1.5a)."""
196
+ try:
197
+ result = subprocess.run(
198
+ [es_path, "-get-everything-version"],
199
+ capture_output=True, text=True, timeout=5,
200
+ creationflags=_CREATE_NO_WINDOW,
201
+ )
202
+ if result.returncode == 0 and result.stdout.strip():
203
+ return ""
204
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
205
+ pass
206
+
207
+ try:
208
+ result = subprocess.run(
209
+ [es_path, "-instance", "1.5a", "-get-everything-version"],
210
+ capture_output=True, text=True, timeout=5,
211
+ creationflags=_CREATE_NO_WINDOW,
212
+ )
213
+ if result.returncode == 0 and result.stdout.strip():
214
+ return "1.5a"
215
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
216
+ pass
217
+
218
+ return ""
219
+
220
+
221
+ def _test_connection(es_path: str, instance: str) -> tuple[bool, str]:
222
+ """Verify Everything is running and responsive."""
223
+ base = [es_path]
224
+ if instance:
225
+ base.extend(["-instance", instance])
226
+
227
+ try:
228
+ result = subprocess.run(
229
+ [*base, "-get-everything-version"],
230
+ capture_output=True, text=True, timeout=10,
231
+ creationflags=_CREATE_NO_WINDOW,
232
+ )
233
+ if result.returncode == 0 and result.stdout.strip():
234
+ return True, f"Everything v{result.stdout.strip()}"
235
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
236
+ pass
237
+
238
+ try:
239
+ result = subprocess.run(
240
+ [*base, "-n", "1", "*.txt"],
241
+ capture_output=True, text=True, timeout=10,
242
+ creationflags=_CREATE_NO_WINDOW,
243
+ )
244
+ if result.returncode == 0:
245
+ return True, "Everything connected (version unknown)"
246
+ err = result.stderr.strip() or result.stdout.strip() or "Unknown error"
247
+ return False, err
248
+ except subprocess.TimeoutExpired:
249
+ return False, "Connection timed out"
250
+ except FileNotFoundError:
251
+ return False, f"es.exe not found at {es_path}"
252
+ except OSError as exc:
253
+ return False, str(exc)
File without changes