mcp-win-stdio 0.1.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,5 @@
1
+ """
2
+ mcp-win-stdio: Windows-optimized Model Context Protocol suite and CLI orchestrator.
3
+ """
4
+
5
+ __version__ = "0.1.0"
mcp_win_stdio/cli.py ADDED
@@ -0,0 +1,375 @@
1
+ """
2
+ mcp-win-stdio CLI: Windows-optimized Model Context Protocol suite orchestrator.
3
+ """
4
+
5
+ import argparse
6
+ import importlib
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ import shutil
11
+ import subprocess
12
+ import sys
13
+ from typing import Dict, List, Optional
14
+
15
+ # Ensure Windows console uses UTF-8 without crashing on cp1252
16
+ if sys.platform == "win32":
17
+ try:
18
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
19
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
20
+ except Exception:
21
+ pass
22
+
23
+ from mcp_win_stdio import __version__
24
+ from mcp_win_stdio.core.config import INDALA_DIR, PLUGINS_DIR, ensure_workspace_dirs, load_config, save_config
25
+ from mcp_win_stdio.core.discovery import BUILTIN_SERVERS, get_server_info, list_available_servers
26
+ from mcp_win_stdio.core.installer import (
27
+ generate_claude_cli_command,
28
+ generate_claude_desktop_snippet,
29
+ get_claude_cli_config_path,
30
+ get_claude_desktop_config_path,
31
+ install_pip_dependencies,
32
+ remove_server_from_cli,
33
+ remove_server_from_desktop,
34
+ safe_apply_to_cli,
35
+ safe_apply_to_desktop,
36
+ )
37
+ from mcp_win_stdio.guides.excel_guide import print_excel_guide
38
+ from mcp_win_stdio.guides.explorer_guide import print_explorer_guide
39
+ from mcp_win_stdio.guides.tsc_guide import print_tsc_guide
40
+ from mcp_win_stdio.guides.word_guide import print_word_guide
41
+
42
+
43
+ def print_dashboard() -> None:
44
+ """Print interactive home dashboard when run with no arguments."""
45
+ ensure_workspace_dirs()
46
+ servers = list_available_servers()
47
+ desktop_cfg = get_claude_desktop_config_path()
48
+ cli_cfg = get_claude_cli_config_path()
49
+
50
+ print(f"\n" + "=" * 76)
51
+ print(f" 🚀 mcp-win-stdio — Windows Model Context Protocol Suite (v{__version__})")
52
+ print("=" * 76)
53
+ print(f" Single-Source Hub: {INDALA_DIR}")
54
+ print(f" Claude Desktop Config: {desktop_cfg or 'Not detected (%APPDATA%\\Claude)'}")
55
+ print(f" Claude Code CLI Config: {cli_cfg or 'Not detected (~/.claude.json)'}")
56
+ print("-" * 76)
57
+
58
+ print(f"{'SERVER':<12} {'STATUS':<16} {'TOOLS':<8} {'DESCRIPTION'}")
59
+ print("-" * 76)
60
+
61
+ for name, srv in servers.items():
62
+ is_inst = srv.get("is_installed", False)
63
+ status_str = "[Installed]" if is_inst else "[Not Installed]"
64
+ tools = str(srv.get("tools_count", "?"))
65
+ desc = srv.get("description", "")
66
+ if len(desc) > 36:
67
+ desc = desc[:33] + "..."
68
+ print(f"{name:<12} {status_str:<16} {tools:<8} {desc}")
69
+
70
+ print("-" * 76)
71
+ print(" 💡 Quick Commands:")
72
+ print(" mws setup <server> -> Install dependencies & show Claude config")
73
+ print(" mws guide <server> -> View complete tool reference & Claude prompts")
74
+ print(" mws doctor -> Run health checks (Office COM, Python, Node)")
75
+ print(" mws run <server> -> Launch MCP server over stdio")
76
+ print(" mws list -> List all servers and custom plugins")
77
+ print("=" * 76 + "\n")
78
+
79
+
80
+ def cmd_list(args: argparse.Namespace) -> None:
81
+ """List available MCP servers and installation status."""
82
+ print_dashboard()
83
+
84
+
85
+ def cmd_guide(args: argparse.Namespace) -> None:
86
+ """Print guide and prompt recipes for a specific server."""
87
+ target = (args.server or "all").lower()
88
+
89
+ if target in ("excel", "all"):
90
+ print_excel_guide()
91
+ if target in ("word", "all"):
92
+ print_word_guide()
93
+ if target in ("explorer", "workspace-explorer", "all"):
94
+ print_explorer_guide()
95
+ if target in ("tsc", "all"):
96
+ print_tsc_guide()
97
+
98
+ if target not in ("excel", "word", "explorer", "workspace-explorer", "tsc", "all"):
99
+ print(f"No built-in guide for '{target}'. Built-in guides: 'excel', 'word', 'explorer', 'tsc'.")
100
+
101
+
102
+ def cmd_setup(args: argparse.Namespace) -> None:
103
+ """Setup and configure a server with transparent guidance and optional safe auto-apply."""
104
+ ensure_workspace_dirs()
105
+ servers = list_available_servers()
106
+
107
+ target = args.server
108
+ client = args.client.lower()
109
+
110
+ if not target:
111
+ if sys.stdin.isatty():
112
+ print("\n=== 🛠️ mcp-win-stdio Setup Wizard ===")
113
+ print("Select an MCP server to configure:")
114
+ print(" [1] excel (20 tools: Pandas queries, RapidFuzz reconciliation, Office COM)")
115
+ print(" [2] word (10 tools: Multi-unit margins, multi-columns, typography, images)")
116
+ print(" [3] explorer (11 tools: Token-safe tree, .gitignore, regex grep, AST outline)")
117
+ print(" [4] tsc (6 tools: TypeScript diagnostic watcher, 0ms cache)")
118
+ print(" [5] all (Configure all servers)")
119
+ print(" [6] Exit")
120
+ choice = input("\nEnter choice (1-6) [default: 1]: ").strip() or "1"
121
+ mapping = {"1": "excel", "2": "word", "3": "explorer", "4": "tsc", "5": "all"}
122
+ if choice not in mapping:
123
+ print("Setup cancelled.")
124
+ return
125
+ target = mapping[choice]
126
+ else:
127
+ target = "all"
128
+
129
+ selected_servers = ["excel", "word", "explorer", "tsc"] if target.lower() == "all" else [target.lower()]
130
+
131
+ for srv_name in selected_servers:
132
+ srv = get_server_info(srv_name)
133
+ if not srv:
134
+ print(f"\n[ERROR] Unknown server: '{srv_name}'. Run 'mws list' to see available servers.")
135
+ continue
136
+
137
+ print(f"\n" + "=" * 70)
138
+ print(f" Configuration Setup for: {srv['title']}")
139
+ print("=" * 70)
140
+
141
+ # 1. Dependency check & optional installation
142
+ env_vars = {}
143
+ if not srv.get("is_installed"):
144
+ req_pip = srv.get("required_pip", [])
145
+ print(f"\n[INFO] '{srv_name}' requires the following Python packages: {', '.join(req_pip)}")
146
+ if sys.stdin.isatty():
147
+ do_install = input(f"Would you like to install them now via pip? [Y/n]: ").strip().lower()
148
+ if do_install not in ("n", "no"):
149
+ ok, msg = install_pip_dependencies(req_pip)
150
+ if ok:
151
+ print(f"[OK] {msg}")
152
+ else:
153
+ print(f"[WARN] {msg}")
154
+ else:
155
+ install_pip_dependencies(req_pip)
156
+
157
+ # Special prompt for TSC watch directory
158
+ if srv_name == "tsc":
159
+ default_dir = os.getcwd()
160
+ if sys.stdin.isatty():
161
+ chosen_dir = input(f"\nEnter TypeScript project directory to watch [default: {default_dir}]: ").strip() or default_dir
162
+ else:
163
+ chosen_dir = default_dir
164
+ env_vars["TSC_WATCH_DIR"] = os.path.abspath(chosen_dir)
165
+
166
+ # 2. Transparent Configuration Guidance
167
+ snippet_dict = generate_claude_desktop_snippet(srv_name, env_vars if env_vars else None)
168
+ snippet_json = json.dumps({srv_name: snippet_dict}, indent=2)
169
+ cli_command = generate_claude_cli_command(srv_name, env_vars if env_vars else None)
170
+
171
+ print("\n" + "-" * 70)
172
+ print("📋 Claude Desktop Configuration:")
173
+ print("File: %APPDATA%\\Claude\\claude_desktop_config.json")
174
+ print("Add this snippet inside your \"mcpServers\" object:\n")
175
+ print(snippet_json)
176
+
177
+ print("\n" + "-" * 70)
178
+ print("💻 Claude Code CLI Command:")
179
+ print("Run this command in your terminal:\n")
180
+ print(f" {cli_command}")
181
+ print("-" * 70)
182
+
183
+ # 3. Optional Safe Auto-Write
184
+ if sys.stdin.isatty():
185
+ auto_apply = input("\n👉 Would you like mws to safely write this configuration for you? [y/N]: ").strip().lower()
186
+ if auto_apply in ("y", "yes"):
187
+ if client in ("all", "desktop"):
188
+ ok, msg = safe_apply_to_desktop(srv_name, env_vars if env_vars else None)
189
+ symbol = "OK" if ok else "ERROR"
190
+ print(f" [{symbol}] Claude Desktop: {msg}")
191
+ if client in ("all", "cli"):
192
+ ok, msg = safe_apply_to_cli(srv_name, env_vars if env_vars else None)
193
+ symbol = "OK" if ok else "ERROR"
194
+ print(f" [{symbol}] Claude Code CLI: {msg}")
195
+ print("\n[NOTE] Please restart Claude Desktop if it is currently running.")
196
+ else:
197
+ print("\nTo auto-apply via script, pass interactive input or use the JSON snippet above.")
198
+
199
+ print("\n" + "=" * 70)
200
+ print("Setup guide complete!")
201
+ print("=" * 70 + "\n")
202
+
203
+
204
+ def cmd_remove(args: argparse.Namespace) -> None:
205
+ """Remove server(s) from Claude Desktop and CLI."""
206
+ target = args.server.lower()
207
+ client = args.client.lower()
208
+
209
+ servers_to_remove = ["excel", "word", "explorer", "tsc"] if target == "all" else [target]
210
+
211
+ for srv_name in servers_to_remove:
212
+ print(f"\nRemoving '{srv_name}'...")
213
+ if client in ("all", "desktop"):
214
+ ok, msg = remove_server_from_desktop(srv_name)
215
+ print(f" Claude Desktop: {msg}")
216
+ if client in ("all", "cli"):
217
+ ok, msg = remove_server_from_cli(srv_name)
218
+ print(f" Claude CLI: {msg}")
219
+
220
+
221
+ def cmd_run(args: argparse.Namespace) -> None:
222
+ """Run an MCP server over stdio."""
223
+ server_name = args.server.lower()
224
+ srv = get_server_info(server_name)
225
+
226
+ if not srv:
227
+ print(f"Error: Server '{server_name}' not found. Run 'mws list' to view available servers.", file=sys.stderr)
228
+ sys.exit(1)
229
+
230
+ if srv["is_builtin"]:
231
+ module_name = srv["module"]
232
+ try:
233
+ mod = importlib.import_module(module_name)
234
+ if hasattr(mod, "mcp"):
235
+ mod.mcp.run()
236
+ else:
237
+ print(f"Error: Module '{module_name}' does not expose 'mcp'.", file=sys.stderr)
238
+ sys.exit(1)
239
+ except Exception as e:
240
+ print(f"Error launching server '{server_name}': {str(e)}", file=sys.stderr)
241
+ sys.exit(1)
242
+ else:
243
+ # Run custom user plugin
244
+ plugin_path = srv["path"]
245
+ try:
246
+ subprocess.run([sys.executable, plugin_path])
247
+ except Exception as e:
248
+ print(f"Error running plugin: {str(e)}", file=sys.stderr)
249
+ sys.exit(1)
250
+
251
+
252
+ def cmd_doctor(args: argparse.Namespace) -> None:
253
+ """Run diagnostic health checks on Windows environment, COM automation, and dependencies."""
254
+ print(f"\n=== mcp-win-stdio Doctor Diagnostic (v{__version__}) ===\n")
255
+
256
+ # 1. Python Check
257
+ py_ver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
258
+ py_status = "OK" if sys.version_info >= (3, 10) else "FAIL (Requires Python 3.10+)"
259
+ print(f"[{py_status}] Python Runtime: {py_ver} ({sys.executable})")
260
+
261
+ # 2. Python Dependencies
262
+ deps = [
263
+ ("mcp", "MCP Protocol Framework (Core)"),
264
+ ("docx", "Python-Docx (Word MCP)"),
265
+ ("pandas", "Pandas DataFrames (Excel MCP)"),
266
+ ("openpyxl", "OpenPyXL Engine (Excel MCP)"),
267
+ ("rapidfuzz", "RapidFuzz Vector Matcher (Excel & Explorer)"),
268
+ ("pathspec", "Git-Wildmatch Pattern Engine (Explorer MCP)"),
269
+ ("win32com", "PyWin32 Windows COM Automation (Excel & Word)"),
270
+ ]
271
+ print("\n--- Python Dependencies ---")
272
+ for mod, desc in deps:
273
+ try:
274
+ importlib.import_module(mod)
275
+ print(f"[OK] {mod:<14} : {desc}")
276
+ except ImportError:
277
+ print(f"[MISSING] {mod:<10} : {desc}")
278
+
279
+ # 3. Microsoft Office COM Automation
280
+ print("\n--- Microsoft Office COM Automation ---")
281
+ try:
282
+ import win32com.client
283
+ excel_app = win32com.client.DispatchEx("Excel.Application")
284
+ excel_ver = excel_app.Version
285
+ excel_app.Quit()
286
+ print(f"[OK] Microsoft Excel COM Automation: Ready (Version {excel_ver})")
287
+ except Exception as e:
288
+ print(f"[INFO] Microsoft Excel COM Automation: Not available ({str(e)})")
289
+
290
+ try:
291
+ import win32com.client
292
+ word_app = win32com.client.Dispatch("Word.Application")
293
+ word_app.Visible = False
294
+ word_ver = word_app.Version
295
+ word_app.Quit()
296
+ print(f"[OK] Microsoft Word COM Automation: Ready (Version {word_ver})")
297
+ except Exception as e:
298
+ print(f"[INFO] Microsoft Word COM Automation: Not available ({str(e)})")
299
+
300
+ # 4. Node.js & TypeScript
301
+ print("\n--- Node.js & TypeScript Environment ---")
302
+ node_bin = shutil.which("node")
303
+ print(f"[{'OK' if node_bin else 'INFO'}] Node.js: {node_bin or 'Not found on PATH'}")
304
+
305
+ tsc_bin = shutil.which("tsc")
306
+ print(f"[{'OK' if tsc_bin else 'INFO'}] Global tsc: {tsc_bin or 'Not found on PATH (will check local projects)'}")
307
+
308
+ # 5. Claude Config Files
309
+ print("\n--- Claude Configuration Targets ---")
310
+ desktop_cfg = get_claude_desktop_config_path()
311
+ if desktop_cfg and desktop_cfg.exists():
312
+ print(f"[OK] Claude Desktop: {desktop_cfg}")
313
+ else:
314
+ print(f"[INFO] Claude Desktop config: {desktop_cfg or 'Not found in %APPDATA%\\Claude'}")
315
+
316
+ cli_cfg = get_claude_cli_config_path()
317
+ if cli_cfg and cli_cfg.exists():
318
+ print(f"[OK] Claude Code CLI: {cli_cfg}")
319
+ else:
320
+ print(f"[INFO] Claude Code CLI config: {cli_cfg or 'Not found in ~/.claude.json'}")
321
+
322
+ print("\nDiagnostic complete.\n")
323
+
324
+
325
+ def main() -> None:
326
+ """CLI entry point."""
327
+ parser = argparse.ArgumentParser(
328
+ prog="mcp-win-stdio",
329
+ description="Windows-optimized Model Context Protocol suite & setup orchestrator.",
330
+ )
331
+ parser.add_argument("--version", "-v", action="version", version=f"%(prog)s {__version__}")
332
+
333
+ subparsers = parser.add_subparsers(dest="command", help="Command to execute")
334
+
335
+ # list
336
+ sub_list = subparsers.add_parser("list", help="List available MCP servers and tool counts")
337
+ sub_list.set_defaults(func=cmd_list)
338
+
339
+ # guide
340
+ sub_guide = subparsers.add_parser("guide", help="View usage guide, tool specs, and LLM prompts")
341
+ sub_guide.add_argument("server", nargs="?", default="all", help="Server name ('excel', 'word', 'explorer', 'tsc', 'all')")
342
+ sub_guide.set_defaults(func=cmd_guide)
343
+
344
+ # setup
345
+ sub_setup = subparsers.add_parser("setup", help="Configure server(s) into Claude Desktop and CLI")
346
+ sub_setup.add_argument("server", nargs="?", default=None, help="Server to install ('excel', 'word', 'explorer', 'tsc', 'all')")
347
+ sub_setup.add_argument("--client", "-c", choices=["all", "desktop", "cli"], default="all", help="Target client")
348
+ sub_setup.set_defaults(func=cmd_setup)
349
+
350
+ # remove
351
+ sub_remove = subparsers.add_parser("remove", help="Remove server(s) from Claude Desktop and CLI")
352
+ sub_remove.add_argument("server", help="Server to remove ('excel', 'word', 'explorer', 'tsc', 'all')")
353
+ sub_remove.add_argument("--client", "-c", choices=["all", "desktop", "cli"], default="all", help="Target client")
354
+ sub_remove.set_defaults(func=cmd_remove)
355
+
356
+ # run
357
+ sub_run = subparsers.add_parser("run", help="Launch an MCP server over stdio for Claude")
358
+ sub_run.add_argument("server", help="Server name to launch ('excel', 'word', 'explorer', 'tsc', or custom plugin)")
359
+ sub_run.set_defaults(func=cmd_run)
360
+
361
+ # doctor
362
+ sub_doctor = subparsers.add_parser("doctor", help="Check system health, dependencies, and COM readiness")
363
+ sub_doctor.set_defaults(func=cmd_doctor)
364
+
365
+ args = parser.parse_args()
366
+ if not args.command:
367
+ # Default action when run with no arguments: show interactive dashboard
368
+ print_dashboard()
369
+ sys.exit(0)
370
+
371
+ args.func(args)
372
+
373
+
374
+ if __name__ == "__main__":
375
+ main()
@@ -0,0 +1,55 @@
1
+ """
2
+ Core configuration and single-source path management for mcp-win-stdio.
3
+ """
4
+
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Any, Dict
9
+
10
+ # Standard single-source directory: ~/.mcp-win-stdio/
11
+ USER_HOME = Path.home()
12
+ INDALA_DIR = USER_HOME / ".mcp-win-stdio"
13
+ PLUGINS_DIR = INDALA_DIR / "plugins"
14
+ LOGS_DIR = INDALA_DIR / "logs"
15
+ EXPORTS_DIR = INDALA_DIR / "exports"
16
+ CONFIG_FILE = INDALA_DIR / "config.json"
17
+
18
+
19
+ def ensure_workspace_dirs() -> Dict[str, Path]:
20
+ """Ensure all core directories exist and return paths."""
21
+ for d in (INDALA_DIR, PLUGINS_DIR, LOGS_DIR, EXPORTS_DIR):
22
+ d.mkdir(parents=True, exist_ok=True)
23
+ if not CONFIG_FILE.exists():
24
+ default_config = {
25
+ "version": "0.1.0",
26
+ "default_client": "all",
27
+ "installed_servers": {},
28
+ "log_level": "INFO",
29
+ }
30
+ with open(CONFIG_FILE, "w", encoding="utf-8") as f:
31
+ json.dump(default_config, f, indent=2)
32
+ return {
33
+ "root": INDALA_DIR,
34
+ "plugins": PLUGINS_DIR,
35
+ "logs": LOGS_DIR,
36
+ "exports": EXPORTS_DIR,
37
+ "config": CONFIG_FILE,
38
+ }
39
+
40
+
41
+ def load_config() -> Dict[str, Any]:
42
+ """Load configuration from ~/.mcp-win-stdio/config.json."""
43
+ ensure_workspace_dirs()
44
+ try:
45
+ with open(CONFIG_FILE, "r", encoding="utf-8") as f:
46
+ return json.load(f)
47
+ except Exception:
48
+ return {}
49
+
50
+
51
+ def save_config(cfg: Dict[str, Any]) -> None:
52
+ """Save configuration to ~/.mcp-win-stdio/config.json."""
53
+ ensure_workspace_dirs()
54
+ with open(CONFIG_FILE, "w", encoding="utf-8") as f:
55
+ json.dump(cfg, f, indent=2)
@@ -0,0 +1,110 @@
1
+ """
2
+ Discovery service for built-in MCP servers and user plugins in ~/.mcp-win-stdio/plugins.
3
+ """
4
+
5
+ import importlib
6
+ import importlib.util
7
+ from pathlib import Path
8
+ import sys
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from mcp_win_stdio.core.config import PLUGINS_DIR, ensure_workspace_dirs
12
+
13
+ BUILTIN_SERVERS = {
14
+ "excel": {
15
+ "name": "excel",
16
+ "title": "Excel MCP (Windows Native + Pandas)",
17
+ "module": "mcp_win_stdio.excel.server",
18
+ "description": "20 tools: Pandas queries, RapidFuzz reconciliation, OpenPyXL editing, and native Excel COM automation (PDF exports, recalc, pivots, macros).",
19
+ "tools_count": 20,
20
+ "is_builtin": True,
21
+ "required_pip": ["pandas>=2.0.0", "openpyxl>=3.1.0", "rapidfuzz>=3.0.0", "pywin32>=306"],
22
+ "dependencies": ["pandas", "openpyxl", "rapidfuzz"],
23
+ "optional_dependencies": ["win32com"],
24
+ },
25
+ "word": {
26
+ "name": "word",
27
+ "title": "Word MCP (Advanced Layout & Typography)",
28
+ "module": "mcp_win_stdio.word.server",
29
+ "description": "10 tools: multi-unit margins (in, cm, mm, pt), multi-column layout, paragraph spacing/indentation, typography (fonts, sizes, colors), images, tables.",
30
+ "tools_count": 10,
31
+ "is_builtin": True,
32
+ "required_pip": ["python-docx>=1.1.0", "pywin32>=306"],
33
+ "dependencies": ["docx"],
34
+ "optional_dependencies": ["win32com"],
35
+ },
36
+ "explorer": {
37
+ "name": "explorer",
38
+ "title": "Workspace Explorer MCP (Smart Tree & Grep)",
39
+ "module": "mcp_win_stdio.explorer.server",
40
+ "description": "11 tools: token-safe collapsible directory trees, .gitignore resolution, in-file grep, RapidFuzz fuzzy search, and Python/TS AST outline.",
41
+ "tools_count": 11,
42
+ "is_builtin": True,
43
+ "required_pip": ["pathspec>=0.12.0", "rapidfuzz>=3.0.0"],
44
+ "dependencies": ["pathspec", "rapidfuzz"],
45
+ "optional_dependencies": [],
46
+ },
47
+ "tsc": {
48
+ "name": "tsc",
49
+ "title": "TypeScript Watcher MCP (0ms Diagnostic Cache)",
50
+ "module": "mcp_win_stdio.tsc.server",
51
+ "description": "6 tools: background tsc compiler watchers, in-memory diagnostic cache, 0ms error checks, and dynamic project switching.",
52
+ "tools_count": 6,
53
+ "is_builtin": True,
54
+ "required_pip": ["mcp>=1.2.0"],
55
+ "dependencies": ["mcp"],
56
+ "optional_dependencies": [],
57
+ },
58
+ }
59
+
60
+
61
+ def check_server_installed(server_info: Dict[str, Any]) -> bool:
62
+ """Check if all mandatory dependencies for a server are importable."""
63
+ if not server_info.get("is_builtin"):
64
+ return Path(server_info.get("path", "")).exists()
65
+
66
+ for dep in server_info.get("dependencies", []):
67
+ try:
68
+ if not importlib.util.find_spec(dep):
69
+ return False
70
+ except Exception:
71
+ return False
72
+ return True
73
+
74
+
75
+ def list_available_servers() -> Dict[str, Dict[str, Any]]:
76
+ """Return dictionary of all available servers with live installation status."""
77
+ ensure_workspace_dirs()
78
+ servers = {}
79
+
80
+ for name, srv in BUILTIN_SERVERS.items():
81
+ data = dict(srv)
82
+ data["is_installed"] = check_server_installed(data)
83
+ servers[name] = data
84
+
85
+ # Discover custom user plugins in ~/.mcp-win-stdio/plugins
86
+ if PLUGINS_DIR.exists():
87
+ for item in PLUGINS_DIR.glob("*.py"):
88
+ if item.name.startswith("__"):
89
+ continue
90
+ plugin_name = item.stem
91
+ servers[plugin_name] = {
92
+ "name": plugin_name,
93
+ "title": f"User Plugin: {plugin_name}",
94
+ "path": str(item),
95
+ "module": None,
96
+ "description": f"Custom user plugin script loaded from {item}",
97
+ "tools_count": "dynamic",
98
+ "is_builtin": False,
99
+ "is_installed": True,
100
+ "required_pip": [],
101
+ "dependencies": [],
102
+ "optional_dependencies": [],
103
+ }
104
+ return servers
105
+
106
+
107
+ def get_server_info(name: str) -> Optional[Dict[str, Any]]:
108
+ """Get metadata and live installation status for a specific server."""
109
+ servers = list_available_servers()
110
+ return servers.get(name.lower())