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.
- mcp_win_stdio/__init__.py +5 -0
- mcp_win_stdio/cli.py +375 -0
- mcp_win_stdio/core/config.py +55 -0
- mcp_win_stdio/core/discovery.py +110 -0
- mcp_win_stdio/core/installer.py +218 -0
- mcp_win_stdio/excel/__init__.py +5 -0
- mcp_win_stdio/excel/__main__.py +8 -0
- mcp_win_stdio/excel/cli.py +186 -0
- mcp_win_stdio/excel/guide.py +52 -0
- mcp_win_stdio/excel/server.py +1011 -0
- mcp_win_stdio/explorer/__init__.py +5 -0
- mcp_win_stdio/explorer/__main__.py +8 -0
- mcp_win_stdio/explorer/cli.py +176 -0
- mcp_win_stdio/explorer/guide.py +53 -0
- mcp_win_stdio/explorer/server.py +1376 -0
- mcp_win_stdio/guides/__init__.py +1 -0
- mcp_win_stdio/guides/excel_guide.py +69 -0
- mcp_win_stdio/guides/explorer_guide.py +68 -0
- mcp_win_stdio/guides/tsc_guide.py +5 -0
- mcp_win_stdio/guides/word_guide.py +5 -0
- mcp_win_stdio/tsc/__init__.py +5 -0
- mcp_win_stdio/tsc/__main__.py +8 -0
- mcp_win_stdio/tsc/cli.py +89 -0
- mcp_win_stdio/tsc/guide.py +56 -0
- mcp_win_stdio/tsc/server.py +388 -0
- mcp_win_stdio/word/__init__.py +5 -0
- mcp_win_stdio/word/__main__.py +8 -0
- mcp_win_stdio/word/cli.py +79 -0
- mcp_win_stdio/word/guide.py +77 -0
- mcp_win_stdio/word/server.py +991 -0
- mcp_win_stdio-0.1.0.dist-info/METADATA +189 -0
- mcp_win_stdio-0.1.0.dist-info/RECORD +35 -0
- mcp_win_stdio-0.1.0.dist-info/WHEEL +4 -0
- mcp_win_stdio-0.1.0.dist-info/entry_points.txt +7 -0
- mcp_win_stdio-0.1.0.dist-info/licenses/LICENSE +21 -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())
|