mcp-win-stdio-tsc 0.1.0__tar.gz

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,60 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ build/
7
+ develop-eggs/
8
+ dist/
9
+ downloads/
10
+ eggs/
11
+ .eggs/
12
+ lib/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ share/python-wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ *.manifest
25
+ *.spec
26
+
27
+ pip-log.txt
28
+ pip-delete-this-directory.txt
29
+
30
+ htmlcov/
31
+ .tox/
32
+ .nox/
33
+ .coverage
34
+ .coverage.*
35
+ .cache
36
+ nosetests.xml
37
+ coverage.xml
38
+ *.cover
39
+ *.py,cover
40
+ .hypothesis/
41
+ .pytest_cache/
42
+ cover/
43
+
44
+ *.mo
45
+ *.pot
46
+
47
+ .env
48
+ .venv
49
+ env/
50
+ venv/
51
+ ENV/
52
+ env.bak/
53
+ venv.bak/
54
+
55
+ .idea/
56
+ .vscode/
57
+ *.swp
58
+ *.swo
59
+
60
+ *.log
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.5
2
+ Name: mcp-win-stdio-tsc
3
+ Version: 0.1.0
4
+ Summary: Windows-optimized TypeScript Diagnostic Watcher MCP server: background watchers and in-memory cache for instant 0ms compiler error checks.
5
+ Author: Mohan Kumar Indala
6
+ License-Expression: MIT
7
+ Keywords: ai,claude,diagnostics,llm,mcp,tsc,typescript,watcher,windows
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Win32 (MS Windows)
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: mcp>=1.2.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # mcp-win-stdio-tsc
25
+
26
+ Windows-optimized **Model Context Protocol (MCP)** server for TypeScript diagnostics.
27
+
28
+ Runs persistent background compiler watchers (`tsc --watch`) with an in-memory cache for **0ms latency** error inspection across multi-project and monorepo repositories.
29
+
30
+ ---
31
+
32
+ ## 🌟 Features (6 Tools)
33
+
34
+ * **0ms In-Memory Cache**: `get_tsc_errors` (all active compiler errors across watched projects, with project/tsconfig filters)
35
+ * **File-Specific Diagnostics**: `get_file_errors` (strictly check errors for a specific `.ts`/`.tsx`/`.js`/`.jsx` file)
36
+ * **Token-Safe Summary**: `get_error_summary` (total error counts, project status, and top 5 most common error codes without flooding context)
37
+ * **Dynamic Multi-Project Control**: `list_watched_projects`, `watch_project(path)`, and `restart_tsc_watcher`
38
+
39
+ ---
40
+
41
+ ## 📦 Installation
42
+
43
+ ```powershell
44
+ pip install mcp-win-stdio-tsc
45
+ ```
46
+
47
+ ---
48
+
49
+ ## 🚀 Usage with Claude Desktop
50
+
51
+ Add to `%APPDATA%\Claude\claude_desktop_config.json`:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "tsc": {
57
+ "command": "python",
58
+ "args": [
59
+ "-m",
60
+ "mcp_win_stdio.tsc"
61
+ ]
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 📜 License
70
+ MIT License. Copyright (c) 2026 Mohan Kumar Indala.
@@ -0,0 +1,47 @@
1
+ # mcp-win-stdio-tsc
2
+
3
+ Windows-optimized **Model Context Protocol (MCP)** server for TypeScript diagnostics.
4
+
5
+ Runs persistent background compiler watchers (`tsc --watch`) with an in-memory cache for **0ms latency** error inspection across multi-project and monorepo repositories.
6
+
7
+ ---
8
+
9
+ ## 🌟 Features (6 Tools)
10
+
11
+ * **0ms In-Memory Cache**: `get_tsc_errors` (all active compiler errors across watched projects, with project/tsconfig filters)
12
+ * **File-Specific Diagnostics**: `get_file_errors` (strictly check errors for a specific `.ts`/`.tsx`/`.js`/`.jsx` file)
13
+ * **Token-Safe Summary**: `get_error_summary` (total error counts, project status, and top 5 most common error codes without flooding context)
14
+ * **Dynamic Multi-Project Control**: `list_watched_projects`, `watch_project(path)`, and `restart_tsc_watcher`
15
+
16
+ ---
17
+
18
+ ## 📦 Installation
19
+
20
+ ```powershell
21
+ pip install mcp-win-stdio-tsc
22
+ ```
23
+
24
+ ---
25
+
26
+ ## 🚀 Usage with Claude Desktop
27
+
28
+ Add to `%APPDATA%\Claude\claude_desktop_config.json`:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "tsc": {
34
+ "command": "python",
35
+ "args": [
36
+ "-m",
37
+ "mcp_win_stdio.tsc"
38
+ ]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ---
45
+
46
+ ## 📜 License
47
+ MIT License. Copyright (c) 2026 Mohan Kumar Indala.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "mcp-win-stdio-tsc"
7
+ version = "0.1.0"
8
+ description = "Windows-optimized TypeScript Diagnostic Watcher MCP server: background watchers and in-memory cache for instant 0ms compiler error checks."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Mohan Kumar Indala" }
14
+ ]
15
+ keywords = ["mcp", "claude", "typescript", "tsc", "watcher", "diagnostics", "windows", "ai", "llm"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Win32 (MS Windows)",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
+ "Topic :: Software Development :: Build Tools",
29
+ ]
30
+
31
+ dependencies = [
32
+ "mcp>=1.2.0",
33
+ ]
34
+
35
+ [project.scripts]
36
+ mcp-win-stdio-tsc = "mcp_win_stdio.tsc.cli:main"
37
+ mws-tsc = "mcp_win_stdio.tsc.cli:main"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/mcp_win_stdio"]
@@ -0,0 +1,5 @@
1
+ """
2
+ mcp_win_stdio.tsc: TypeScript Diagnostic Watcher MCP Server.
3
+ """
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,8 @@
1
+ """
2
+ Direct module execution for mcp_win_stdio.tsc.
3
+ """
4
+
5
+ from mcp_win_stdio.tsc.server import mcp
6
+
7
+ if __name__ == "__main__":
8
+ mcp.run()
@@ -0,0 +1,89 @@
1
+ """
2
+ CLI entry point for mcp-win-stdio-tsc.
3
+ """
4
+
5
+ import argparse
6
+ import os
7
+ import shutil
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ from mcp_win_stdio.tsc import __version__
12
+ from mcp_win_stdio.tsc.guide import print_guide
13
+ from mcp_win_stdio.tsc.server import mcp
14
+
15
+
16
+ def cmd_run(args: argparse.Namespace) -> None:
17
+ """Run TypeScript Watcher MCP server over stdio."""
18
+ if args.dir:
19
+ os.environ["TSC_WATCH_DIR"] = os.path.abspath(args.dir)
20
+ mcp.run()
21
+
22
+
23
+ def cmd_doctor(args: argparse.Namespace) -> None:
24
+ """Check TypeScript watcher health and dependencies."""
25
+ print(f"\n=== mcp-win-stdio-tsc Doctor Diagnostic (v{__version__}) ===\n")
26
+ print(f"[OK] Python: {sys.version.split()[0]} ({sys.executable})")
27
+
28
+ for dep in ("mcp",):
29
+ try:
30
+ __import__(dep)
31
+ print(f"[OK] Python Dependency: {dep:<12}")
32
+ except ImportError:
33
+ print(f"[FAIL] Python Dependency: {dep:<12} (MISSING - run `pip install mcp`)")
34
+
35
+ print("\n--- TypeScript & Node.js System Tools ---")
36
+ node_bin = shutil.which("node")
37
+ if node_bin:
38
+ print(f"[OK] Node.js Runtime: Found ({node_bin})")
39
+ else:
40
+ print("[WARN] Node.js Runtime: Not found on PATH")
41
+
42
+ tsc_bin = shutil.which("tsc")
43
+ if tsc_bin:
44
+ print(f"[OK] Global TypeScript Compiler (tsc): Found ({tsc_bin})")
45
+ else:
46
+ print("[INFO] Global TypeScript Compiler (tsc): Not found on PATH (will check local node_modules/npx)")
47
+
48
+ npx_bin = shutil.which("npx")
49
+ if npx_bin:
50
+ print(f"[OK] NPX Runner: Found ({npx_bin})")
51
+ else:
52
+ print("[INFO] NPX Runner: Not found")
53
+
54
+ target_dir = os.environ.get("TSC_WATCH_DIR") or os.getcwd()
55
+ print(f"\n[INFO] Target Watch Directory: {target_dir}")
56
+ print("\nDiagnostic complete.\n")
57
+
58
+
59
+ def main() -> None:
60
+ parser = argparse.ArgumentParser(
61
+ prog="mcp-win-stdio-tsc",
62
+ description="Windows-optimized TypeScript Diagnostic Watcher MCP CLI",
63
+ )
64
+ parser.add_argument("--version", "-v", action="version", version=f"%(prog)s {__version__}")
65
+ subparsers = parser.add_subparsers(dest="command", help="Command to execute")
66
+
67
+ sub_guide = subparsers.add_parser("guide", help="View usage guide and prompt recipes")
68
+ sub_guide.set_defaults(func=lambda args: print_guide())
69
+
70
+ sub_run = subparsers.add_parser("run", help="Run TypeScript Watcher MCP server over stdio")
71
+ sub_run.add_argument("--dir", "-d", help="Target project directory to watch")
72
+ sub_run.set_defaults(func=cmd_run)
73
+
74
+ sub_doctor = subparsers.add_parser("doctor", help="Check dependencies and TypeScript compiler status")
75
+ sub_doctor.set_defaults(func=cmd_doctor)
76
+
77
+ args = parser.parse_args()
78
+ if not args.command:
79
+ if not sys.stdin.isatty():
80
+ mcp.run()
81
+ return
82
+ parser.print_help()
83
+ sys.exit(0)
84
+
85
+ args.func(args)
86
+
87
+
88
+ if __name__ == "__main__":
89
+ main()
@@ -0,0 +1,56 @@
1
+ """
2
+ Usage guide and Claude prompt recipes for TypeScript Watcher MCP server.
3
+ """
4
+
5
+ def print_guide() -> None:
6
+ guide_text = """
7
+ ================================================================================
8
+ TypeScript Diagnostic Watcher MCP Server (mcp-win-stdio.tsc)
9
+ ================================================================================
10
+
11
+ Description:
12
+ Background TypeScript file watcher maintaining an in-memory diagnostic cache
13
+ for instant (0ms latency) TypeScript error checking across multi-tsconfig projects.
14
+
15
+ Available Tools (6 Tools):
16
+ --------------------------------------------------------------------------------
17
+ 1. get_tsc_errors
18
+ - Returns all active TypeScript compilation errors from cache.
19
+ - Optional filters: project_path (str), tsconfig_path (str)
20
+
21
+ 2. get_file_errors
22
+ - Returns TypeScript errors for a specific .ts / .tsx / .js / .jsx file.
23
+ - Args: file_path (str)
24
+
25
+ 3. get_error_summary
26
+ - Returns error counts per project and top 5 most common error codes (e.g. TS2322).
27
+
28
+ 4. list_watched_projects
29
+ - Lists all active tsconfig.json files and current watcher statuses.
30
+
31
+ 5. watch_project
32
+ - Dynamically adds a new project directory to watch without restarting the server.
33
+ - Args: project_path (str)
34
+
35
+ 6. restart_tsc_watcher
36
+ - Restarts all watchers and flushes diagnostics.
37
+
38
+ --------------------------------------------------------------------------------
39
+ Environment Variables:
40
+ --------------------------------------------------------------------------------
41
+ * TSC_WATCH_DIR: Base directory to scan and watch for tsconfig.json files.
42
+ (Defaults to current workspace directory if not specified).
43
+
44
+ --------------------------------------------------------------------------------
45
+ Example Prompts for Claude:
46
+ --------------------------------------------------------------------------------
47
+ * "Check if there are any TypeScript errors in my project right now."
48
+ * "Are there any type errors in src/components/Dashboard.tsx?"
49
+ * "Watch the project located at 'Z:/projects/IJITEST Main' for TypeScript diagnostics."
50
+ ================================================================================
51
+ """
52
+ print(guide_text)
53
+
54
+
55
+ def print_tsc_guide() -> None:
56
+ print_guide()
@@ -0,0 +1,474 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ TypeScript Diagnostic Watcher MCP Server (Python Edition).
4
+ Runs background tsc compiler watchers for target projects, maintaining an in-memory
5
+ diagnostic cache for instantaneous (0ms latency) TypeScript error inspection.
6
+ """
7
+
8
+ import atexit
9
+ from datetime import datetime, timezone
10
+ import json
11
+ import os
12
+ from pathlib import Path
13
+ import re
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ import threading
18
+ import time
19
+ from typing import Any, Dict, List, Optional
20
+
21
+ from mcp.server.fastmcp import FastMCP
22
+
23
+ mcp = FastMCP("tsc-mcp")
24
+
25
+ # Global in-memory cache and watcher state
26
+ # tsconfig_path -> { "process": Popen, "thread": Thread, "project_dir": str, "errors": list, "pending_errors": list, "status": str, "last_updated": str }
27
+ WATCHED_PROJECTS: Dict[str, Dict[str, Any]] = {}
28
+ CACHE_LOCK = threading.Lock()
29
+
30
+ # Regex to parse standard tsc compiler output line
31
+ # Example: src/components/App.tsx(42,15): error TS2322: Type 'string' is not assignable to type 'number'.
32
+ ERROR_REGEX = re.compile(r"^([^(]+)\((\d+),(\d+)\):\s*(error|warning)\s*(TS\d+):\s*(.+)$")
33
+
34
+
35
+ def is_home_or_root_dir(p: str) -> bool:
36
+ """Check if path is the user home directory, root drive, or Windows system directory."""
37
+ try:
38
+ resolved = Path(p).resolve()
39
+ if resolved == Path.home().resolve() or resolved.parent == resolved:
40
+ return True
41
+ windir = os.environ.get("WINDIR", "C:\\Windows")
42
+ if str(resolved).lower().startswith(windir.lower()):
43
+ return True
44
+ except Exception:
45
+ pass
46
+ return False
47
+
48
+
49
+ def get_default_watch_dir() -> str:
50
+ """Get project directory from env, config file, or current working directory."""
51
+ env_dir = os.environ.get("TSC_WATCH_DIR") or os.environ.get("PROJECT_ROOT")
52
+ if env_dir and os.path.isdir(env_dir):
53
+ return os.path.abspath(env_dir)
54
+
55
+ # Check ~/.mcp-win-stdio/config.json
56
+ try:
57
+ from mcp_win_stdio.core.config import load_config
58
+ cfg = load_config()
59
+ stored_dir = cfg.get("tsc_watch_dir")
60
+ if stored_dir and os.path.isdir(stored_dir):
61
+ return os.path.abspath(stored_dir)
62
+ except Exception:
63
+ pass
64
+
65
+ return os.path.abspath(os.getcwd())
66
+
67
+
68
+
69
+ def normalize_path(p: str) -> str:
70
+ return os.path.abspath(p).replace("\\", "/")
71
+
72
+
73
+ def find_tsconfigs(base_dir: str, max_depth: int = 4) -> List[str]:
74
+ """Find all tsconfig.json files skipping heavy build/vendor folders."""
75
+ ignore_dirs = {
76
+ "node_modules", ".git", ".next", ".nuxt", "dist", "build",
77
+ "out", ".output", "target", "bin", "obj", "__pycache__", ".cache",
78
+ "coverage", ".turbo"
79
+ }
80
+ configs = []
81
+ base_p = Path(base_dir)
82
+ if not base_p.exists():
83
+ return configs
84
+
85
+ for root, dirs, files in os.walk(base_dir):
86
+ # Prune ignored directories in-place
87
+ dirs[:] = [d for d in dirs if d.lower() not in ignore_dirs]
88
+ rel_depth = len(Path(root).relative_to(base_p).parts)
89
+ if rel_depth > max_depth:
90
+ dirs.clear()
91
+ continue
92
+
93
+ if "tsconfig.json" in files:
94
+ configs.append(os.path.join(root, "tsconfig.json"))
95
+
96
+ return configs
97
+
98
+
99
+ def resolve_tsc_command(project_dir: str) -> Optional[List[str]]:
100
+ """Find local or global tsc binary."""
101
+ # 1. Local project node_modules/typescript/bin/tsc
102
+ local_tsc_js = Path(project_dir) / "node_modules" / "typescript" / "bin" / "tsc"
103
+ if local_tsc_js.exists():
104
+ node_bin = shutil.which("node") or "node"
105
+ return [node_bin, str(local_tsc_js)]
106
+
107
+ # 2. Local node_modules/.bin/tsc.cmd on Windows
108
+ local_tsc_cmd = Path(project_dir) / "node_modules" / ".bin" / "tsc.cmd"
109
+ if local_tsc_cmd.exists():
110
+ return [str(local_tsc_cmd)]
111
+
112
+ # 3. Global tsc on PATH
113
+ global_tsc = shutil.which("tsc")
114
+ if global_tsc:
115
+ return [global_tsc]
116
+
117
+ # 4. Fallback to npx tsc
118
+ npx_bin = shutil.which("npx")
119
+ if npx_bin:
120
+ return [npx_bin, "tsc"]
121
+
122
+ return None
123
+
124
+
125
+ def parse_tsc_line(line: str, project_dir: str, root_dir: str) -> Optional[Dict[str, Any]]:
126
+ """Parse single tsc diagnostic error line into structured dict."""
127
+ trimmed = line.strip()
128
+ match = ERROR_REGEX.match(trimmed)
129
+ if not match:
130
+ return None
131
+
132
+ raw_file, line_num, col_num, severity, code, message = match.groups()
133
+ abs_path = os.path.abspath(raw_file if os.path.isabs(raw_file) else os.path.join(project_dir, raw_file))
134
+ try:
135
+ rel_path = os.path.relpath(abs_path, root_dir)
136
+ except Exception:
137
+ rel_path = abs_path
138
+
139
+ return {
140
+ "file": normalize_path(abs_path),
141
+ "relative_path": normalize_path(rel_path),
142
+ "line": int(line_num),
143
+ "column": int(col_num),
144
+ "severity": severity.lower(),
145
+ "code": code,
146
+ "message": message.strip(),
147
+ }
148
+
149
+
150
+ def _watcher_loop(tsconfig_path: str, root_dir: str) -> None:
151
+ """Worker thread running tsc --noEmit --watch in background."""
152
+ project_dir = os.path.dirname(tsconfig_path)
153
+ cmd_base = resolve_tsc_command(project_dir)
154
+
155
+ if not cmd_base:
156
+ with CACHE_LOCK:
157
+ if tsconfig_path in WATCHED_PROJECTS:
158
+ WATCHED_PROJECTS[tsconfig_path]["status"] = "error: tsc binary not found (install typescript)"
159
+ return
160
+
161
+ full_cmd = cmd_base + ["--noEmit", "--watch", "--preserveWatchOutput", "-p", tsconfig_path]
162
+
163
+ try:
164
+ # Create background process without opening visible window
165
+ startupinfo = None
166
+ if os.name == "nt":
167
+ startupinfo = subprocess.STARTUPINFO()
168
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
169
+
170
+ proc = subprocess.Popen(
171
+ full_cmd,
172
+ stdout=subprocess.PIPE,
173
+ stderr=subprocess.STDOUT,
174
+ stdin=subprocess.DEVNULL,
175
+ text=True,
176
+ bufsize=1,
177
+ cwd=project_dir,
178
+ startupinfo=startupinfo,
179
+ )
180
+
181
+ with CACHE_LOCK:
182
+ if tsconfig_path in WATCHED_PROJECTS:
183
+ WATCHED_PROJECTS[tsconfig_path]["process"] = proc
184
+ WATCHED_PROJECTS[tsconfig_path]["status"] = "watching"
185
+
186
+ pending_errors: List[Dict[str, Any]] = []
187
+
188
+ for raw_line in proc.stdout:
189
+ line = raw_line.rstrip()
190
+ if not line:
191
+ continue
192
+
193
+ # Check completion markers
194
+ if "Found 0 errors." in line or ("Found " in line and "Watching for file changes." in line):
195
+ with CACHE_LOCK:
196
+ if tsconfig_path in WATCHED_PROJECTS:
197
+ WATCHED_PROJECTS[tsconfig_path]["errors"] = list(pending_errors)
198
+ WATCHED_PROJECTS[tsconfig_path]["status"] = "watching"
199
+ WATCHED_PROJECTS[tsconfig_path]["last_updated"] = datetime.now(timezone.utc).isoformat()
200
+ pending_errors = []
201
+ elif "Starting compilation in watch mode..." in line or "File change detected." in line:
202
+ pending_errors = []
203
+ else:
204
+ err = parse_tsc_line(line, project_dir, root_dir)
205
+ if err:
206
+ pending_errors.append(err)
207
+ elif pending_errors and (line.startswith(" ") or line.startswith("\t")):
208
+ # Multi-line diagnostic message continuation
209
+ pending_errors[-1]["message"] += "\n" + line.strip()
210
+
211
+ except Exception as e:
212
+ with CACHE_LOCK:
213
+ if tsconfig_path in WATCHED_PROJECTS:
214
+ WATCHED_PROJECTS[tsconfig_path]["status"] = f"error: {str(e)}"
215
+
216
+
217
+ def start_watching_project(target_dir: str) -> List[str]:
218
+ """Scan and start background watchers for all tsconfig.json in target_dir."""
219
+ abs_root = os.path.abspath(target_dir)
220
+ configs = find_tsconfigs(abs_root)
221
+
222
+ for cfg in configs:
223
+ norm_cfg = normalize_path(cfg)
224
+ with CACHE_LOCK:
225
+ if norm_cfg in WATCHED_PROJECTS and WATCHED_PROJECTS[norm_cfg].get("process"):
226
+ # Already watching
227
+ continue
228
+
229
+ WATCHED_PROJECTS[norm_cfg] = {
230
+ "project_dir": normalize_path(os.path.dirname(cfg)),
231
+ "tsconfig_path": norm_cfg,
232
+ "relative_config": normalize_path(os.path.relpath(cfg, abs_root)),
233
+ "errors": [],
234
+ "status": "initializing",
235
+ "last_updated": datetime.now(timezone.utc).isoformat(),
236
+ "process": None,
237
+ }
238
+
239
+ t = threading.Thread(target=_watcher_loop, args=(norm_cfg, abs_root), daemon=True)
240
+ t.start()
241
+
242
+ return configs
243
+
244
+
245
+ # Cleanup processes on exit
246
+ def _cleanup_watchers():
247
+ with CACHE_LOCK:
248
+ for srv in WATCHED_PROJECTS.values():
249
+ proc = srv.get("process")
250
+ if proc:
251
+ try:
252
+ proc.terminate()
253
+ except Exception:
254
+ pass
255
+
256
+ atexit.register(_cleanup_watchers)
257
+
258
+ # Initialize on import if explicitly configured or running in a project workspace
259
+ _initial_root = get_default_watch_dir()
260
+ if os.path.isdir(_initial_root):
261
+ is_explicit = bool(os.environ.get("TSC_WATCH_DIR") or os.environ.get("PROJECT_ROOT"))
262
+ if not is_explicit:
263
+ has_project_marker = (
264
+ (Path(_initial_root) / "tsconfig.json").exists()
265
+ or (Path(_initial_root) / "package.json").exists()
266
+ )
267
+ if not is_home_or_root_dir(_initial_root) and has_project_marker:
268
+ start_watching_project(_initial_root)
269
+ else:
270
+ start_watching_project(_initial_root)
271
+
272
+
273
+
274
+ # ==========================================
275
+ # MCP TOOLS
276
+ # ==========================================
277
+
278
+ @mcp.tool()
279
+ def get_tsc_errors(project_path: Optional[str] = None, tsconfig_path: Optional[str] = None) -> Dict[str, Any]:
280
+ """
281
+ Get all active TypeScript compiler errors across watched projects (0ms latency from memory).
282
+ Can filter by specific project directory or tsconfig.json path.
283
+ """
284
+ with CACHE_LOCK:
285
+ if not WATCHED_PROJECTS:
286
+ return {
287
+ "total_errors": 0,
288
+ "projects": [],
289
+ "errors": [],
290
+ "warning": "No TypeScript projects are currently being watched. Call 'watch_project(project_path)' with your project directory first, or configure TSC_WATCH_DIR.",
291
+ }
292
+
293
+ all_errors = []
294
+ projects_summary = []
295
+ initializing_count = 0
296
+
297
+ for cfg_path, data in WATCHED_PROJECTS.items():
298
+ if tsconfig_path and normalize_path(tsconfig_path) != cfg_path:
299
+ continue
300
+ if project_path and normalize_path(project_path) not in data["project_dir"]:
301
+ continue
302
+
303
+ errs = data.get("errors", [])
304
+ status = data.get("status", "unknown")
305
+ if status == "initializing":
306
+ initializing_count += 1
307
+
308
+ all_errors.extend(errs)
309
+ projects_summary.append({
310
+ "tsconfig": data["relative_config"],
311
+ "project_dir": data["project_dir"],
312
+ "status": status,
313
+ "error_count": len(errs),
314
+ "last_updated": data["last_updated"],
315
+ })
316
+
317
+ result: Dict[str, Any] = {
318
+ "total_errors": len(all_errors),
319
+ "projects": projects_summary,
320
+ "errors": all_errors,
321
+ }
322
+ if initializing_count > 0:
323
+ result["notice"] = f"{initializing_count} project(s) still compiling initial pass. Errors may update in a few seconds."
324
+
325
+ return result
326
+
327
+
328
+ @mcp.tool()
329
+ def get_file_errors(file_path: str) -> Dict[str, Any]:
330
+ """
331
+ Get TypeScript compiler errors for a specific file (.ts, .tsx, .js, .jsx).
332
+ """
333
+ with CACHE_LOCK:
334
+ if not WATCHED_PROJECTS:
335
+ return {
336
+ "file": file_path,
337
+ "error_count": 0,
338
+ "has_errors": False,
339
+ "errors": [],
340
+ "warning": "No TypeScript projects are currently being watched. Call 'watch_project(project_path)' with your project directory first.",
341
+ }
342
+
343
+ norm_file = normalize_path(file_path)
344
+ file_errors = []
345
+
346
+ for cfg_path, data in WATCHED_PROJECTS.items():
347
+ for err in data.get("errors", []):
348
+ if err["file"] == norm_file or err["relative_path"] == norm_file or file_path.replace("\\", "/") in err["file"]:
349
+ file_errors.append(err)
350
+
351
+ return {
352
+ "file": file_path,
353
+ "error_count": len(file_errors),
354
+ "has_errors": len(file_errors) > 0,
355
+ "errors": file_errors,
356
+ }
357
+
358
+
359
+ @mcp.tool()
360
+ def get_error_summary() -> Dict[str, Any]:
361
+ """
362
+ Get high-level summary of TypeScript errors across all projects without full diagnostic lists.
363
+ """
364
+ with CACHE_LOCK:
365
+ if not WATCHED_PROJECTS:
366
+ return {
367
+ "total_errors": 0,
368
+ "projects_count": 0,
369
+ "projects": {},
370
+ "warning": "No TypeScript projects are currently being watched. Call 'watch_project(project_path)' with your project directory first.",
371
+ }
372
+
373
+ summary = {}
374
+ total = 0
375
+ error_by_code: Dict[str, int] = {}
376
+ initializing_count = 0
377
+
378
+ for cfg_path, data in WATCHED_PROJECTS.items():
379
+ errs = data.get("errors", [])
380
+ count = len(errs)
381
+ total += count
382
+ status = data.get("status", "unknown")
383
+ if status == "initializing":
384
+ initializing_count += 1
385
+ summary[data["relative_config"]] = {
386
+ "project_dir": data["project_dir"],
387
+ "status": status,
388
+ "errors": count,
389
+ }
390
+ for e in errs:
391
+ code = e.get("code", "UNKNOWN")
392
+ error_by_code[code] = error_by_code.get(code, 0) + 1
393
+
394
+ result: Dict[str, Any] = {
395
+ "total_errors": total,
396
+ "projects_count": len(WATCHED_PROJECTS),
397
+ "projects": summary,
398
+ "most_common_error_codes": sorted(error_by_code.items(), key=lambda x: x[1], reverse=True)[:5],
399
+ }
400
+ if initializing_count > 0:
401
+ result["notice"] = f"{initializing_count} project(s) still compiling initial pass."
402
+
403
+ return result
404
+
405
+
406
+ @mcp.tool()
407
+ def list_watched_projects() -> Dict[str, Any]:
408
+ """
409
+ List all active TypeScript projects and tsconfig.json files currently being monitored.
410
+ """
411
+ with CACHE_LOCK:
412
+ projects = []
413
+ for cfg_path, data in WATCHED_PROJECTS.items():
414
+ projects.append({
415
+ "tsconfig_path": cfg_path,
416
+ "relative_config": data["relative_config"],
417
+ "project_dir": data["project_dir"],
418
+ "status": data["status"],
419
+ "error_count": len(data.get("errors", [])),
420
+ "last_updated": data["last_updated"],
421
+ })
422
+
423
+ return {
424
+ "watched_projects_count": len(projects),
425
+ "projects": projects,
426
+ }
427
+
428
+
429
+ @mcp.tool()
430
+ def watch_project(project_path: str) -> Dict[str, Any]:
431
+ """
432
+ Dynamically add and watch a new TypeScript project or directory without restarting the MCP server.
433
+ """
434
+ if not os.path.exists(project_path):
435
+ return {"success": False, "error": f"Path not found: {project_path}"}
436
+
437
+ # If user or LLM passed a file path, resolve to its containing directory
438
+ if os.path.isfile(project_path):
439
+ project_path = os.path.dirname(project_path)
440
+
441
+ configs = start_watching_project(project_path)
442
+ if not configs:
443
+ return {
444
+ "success": False,
445
+ "project_path": normalize_path(project_path),
446
+ "found_tsconfigs": [],
447
+ "error": f"No tsconfig.json found in '{project_path}' (searched up to 4 directories deep). Make sure this directory contains a TypeScript project.",
448
+ }
449
+
450
+ return {
451
+ "success": True,
452
+ "project_path": normalize_path(project_path),
453
+ "found_tsconfigs": configs,
454
+ "message": f"Started background TypeScript watchers for {len(configs)} configuration(s).",
455
+ }
456
+
457
+
458
+
459
+ @mcp.tool()
460
+ def restart_tsc_watcher() -> Dict[str, Any]:
461
+ """
462
+ Restart all active background TypeScript watchers and refresh diagnostics.
463
+ """
464
+ _cleanup_watchers()
465
+ with CACHE_LOCK:
466
+ WATCHED_PROJECTS.clear()
467
+
468
+ default_root = get_default_watch_dir()
469
+ configs = start_watching_project(default_root)
470
+ return {
471
+ "success": True,
472
+ "restarted_configs": configs,
473
+ "message": f"Restarted watchers for {len(configs)} project(s).",
474
+ }