@treedy/lsp-mcp 0.1.7 → 0.1.8

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.
Files changed (76) hide show
  1. package/dist/bundled/pyright/dist/index.d.ts +2 -0
  2. package/dist/bundled/pyright/dist/index.js +1620 -0
  3. package/dist/bundled/pyright/dist/index.js.map +26 -0
  4. package/dist/bundled/pyright/dist/lsp/connection.d.ts +71 -0
  5. package/dist/bundled/pyright/dist/lsp/document-manager.d.ts +67 -0
  6. package/dist/bundled/pyright/dist/lsp/index.d.ts +3 -0
  7. package/dist/bundled/pyright/dist/lsp/types.d.ts +55 -0
  8. package/dist/bundled/pyright/dist/lsp-client.d.ts +55 -0
  9. package/dist/bundled/pyright/dist/tools/completions.d.ts +18 -0
  10. package/dist/bundled/pyright/dist/tools/definition.d.ts +16 -0
  11. package/dist/bundled/pyright/dist/tools/diagnostics.d.ts +12 -0
  12. package/dist/bundled/pyright/dist/tools/hover.d.ts +16 -0
  13. package/dist/bundled/pyright/dist/tools/references.d.ts +16 -0
  14. package/dist/bundled/pyright/dist/tools/rename.d.ts +18 -0
  15. package/dist/bundled/pyright/dist/tools/search.d.ts +20 -0
  16. package/dist/bundled/pyright/dist/tools/signature-help.d.ts +16 -0
  17. package/dist/bundled/pyright/dist/tools/status.d.ts +14 -0
  18. package/dist/bundled/pyright/dist/tools/symbols.d.ts +17 -0
  19. package/dist/bundled/pyright/dist/tools/update-document.d.ts +14 -0
  20. package/dist/bundled/pyright/dist/utils/position.d.ts +33 -0
  21. package/dist/bundled/pyright/package.json +54 -0
  22. package/dist/bundled/python/README.md +230 -0
  23. package/dist/bundled/python/pyproject.toml +61 -0
  24. package/dist/bundled/python/src/rope_mcp/__init__.py +3 -0
  25. package/dist/bundled/python/src/rope_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  26. package/dist/bundled/python/src/rope_mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  27. package/dist/bundled/python/src/rope_mcp/__pycache__/config.cpython-312.pyc +0 -0
  28. package/dist/bundled/python/src/rope_mcp/__pycache__/config.cpython-313.pyc +0 -0
  29. package/dist/bundled/python/src/rope_mcp/__pycache__/pyright_client.cpython-313.pyc +0 -0
  30. package/dist/bundled/python/src/rope_mcp/__pycache__/rope_client.cpython-313.pyc +0 -0
  31. package/dist/bundled/python/src/rope_mcp/__pycache__/server.cpython-312.pyc +0 -0
  32. package/dist/bundled/python/src/rope_mcp/__pycache__/server.cpython-313.pyc +0 -0
  33. package/dist/bundled/python/src/rope_mcp/config.py +408 -0
  34. package/dist/bundled/python/src/rope_mcp/lsp/__init__.py +15 -0
  35. package/dist/bundled/python/src/rope_mcp/lsp/__pycache__/__init__.cpython-313.pyc +0 -0
  36. package/dist/bundled/python/src/rope_mcp/lsp/__pycache__/client.cpython-313.pyc +0 -0
  37. package/dist/bundled/python/src/rope_mcp/lsp/__pycache__/types.cpython-313.pyc +0 -0
  38. package/dist/bundled/python/src/rope_mcp/lsp/client.py +624 -0
  39. package/dist/bundled/python/src/rope_mcp/lsp/types.py +82 -0
  40. package/dist/bundled/python/src/rope_mcp/pyright_client.py +147 -0
  41. package/dist/bundled/python/src/rope_mcp/rope_client.py +198 -0
  42. package/dist/bundled/python/src/rope_mcp/server.py +963 -0
  43. package/dist/bundled/python/src/rope_mcp/tools/__init__.py +26 -0
  44. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  45. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/change_signature.cpython-313.pyc +0 -0
  46. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/completions.cpython-313.pyc +0 -0
  47. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/definition.cpython-313.pyc +0 -0
  48. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/diagnostics.cpython-313.pyc +0 -0
  49. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/hover.cpython-313.pyc +0 -0
  50. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/move.cpython-313.pyc +0 -0
  51. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/references.cpython-313.pyc +0 -0
  52. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/rename.cpython-313.pyc +0 -0
  53. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/search.cpython-313.pyc +0 -0
  54. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/symbols.cpython-313.pyc +0 -0
  55. package/dist/bundled/python/src/rope_mcp/tools/change_signature.py +184 -0
  56. package/dist/bundled/python/src/rope_mcp/tools/completions.py +84 -0
  57. package/dist/bundled/python/src/rope_mcp/tools/definition.py +51 -0
  58. package/dist/bundled/python/src/rope_mcp/tools/diagnostics.py +18 -0
  59. package/dist/bundled/python/src/rope_mcp/tools/hover.py +49 -0
  60. package/dist/bundled/python/src/rope_mcp/tools/move.py +81 -0
  61. package/dist/bundled/python/src/rope_mcp/tools/references.py +60 -0
  62. package/dist/bundled/python/src/rope_mcp/tools/rename.py +61 -0
  63. package/dist/bundled/python/src/rope_mcp/tools/search.py +128 -0
  64. package/dist/bundled/python/src/rope_mcp/tools/symbols.py +118 -0
  65. package/dist/bundled/python/uv.lock +979 -0
  66. package/dist/bundled/typescript/dist/index.js +29534 -0
  67. package/dist/bundled/typescript/dist/index.js.map +211 -0
  68. package/dist/bundled/typescript/package.json +46 -0
  69. package/dist/bundled/vue/dist/index.d.ts +8 -0
  70. package/dist/bundled/vue/dist/index.js +21111 -0
  71. package/dist/bundled/vue/dist/ts-vue-service.d.ts +67 -0
  72. package/dist/bundled/vue/dist/vue-service.d.ts +144 -0
  73. package/dist/bundled/vue/package.json +45 -0
  74. package/dist/index.js +148 -58
  75. package/dist/index.js.map +4 -4
  76. package/package.json +1 -1
@@ -0,0 +1,408 @@
1
+ """Configuration for Python LSP MCP Server."""
2
+
3
+ import json
4
+ import sys
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from pathlib import Path
8
+ from typing import Optional
9
+ import os
10
+
11
+ try:
12
+ import tomllib # type: ignore[import-not-found]
13
+ except ImportError:
14
+ import tomli as tomllib # type: ignore[import-not-found,import-untyped]
15
+
16
+
17
+ class Backend(Enum):
18
+ """Available backends for code analysis."""
19
+
20
+ ROPE = "rope"
21
+ PYRIGHT = "pyright"
22
+
23
+
24
+ # Tools that support both backends
25
+ SHARED_TOOLS = {"hover", "definition", "references", "completions", "symbols"}
26
+
27
+ # Tools exclusive to each backend
28
+ ROPE_ONLY_TOOLS = {"rename"} # Rope has better refactoring
29
+ PYRIGHT_ONLY_TOOLS = {"diagnostics", "signature_help"} # Pyright exclusive
30
+
31
+ # Environment variable prefix (supports both old and new names)
32
+ ENV_PREFIXES = ["PYTHON_LSP_MCP_", "ROPE_MCP_"]
33
+
34
+
35
+ @dataclass
36
+ class ServerConfig:
37
+ """Configuration for the MCP server."""
38
+
39
+ # Default backend for shared features
40
+ default_backend: Backend = Backend.ROPE
41
+
42
+ # Per-tool backend overrides (None = use default)
43
+ tool_backends: dict[str, Backend] = field(default_factory=dict)
44
+
45
+ @classmethod
46
+ def from_env(cls) -> "ServerConfig":
47
+ """Create config from environment variables.
48
+
49
+ Environment variables (PYTHON_LSP_MCP_ or ROPE_MCP_ prefix):
50
+ *_BACKEND: Default backend (rope/pyright), default: rope
51
+ *_HOVER_BACKEND: Backend for hover (rope/pyright)
52
+ *_DEFINITION_BACKEND: Backend for definition
53
+ *_REFERENCES_BACKEND: Backend for references
54
+ *_COMPLETIONS_BACKEND: Backend for completions
55
+ *_SYMBOLS_BACKEND: Backend for symbols
56
+ """
57
+
58
+ def get_env(suffix: str) -> Optional[str]:
59
+ """Get environment variable with any prefix."""
60
+ for prefix in ENV_PREFIXES:
61
+ val = os.environ.get(f"{prefix}{suffix}", "").lower()
62
+ if val:
63
+ return val
64
+ return None
65
+
66
+ default = get_env("BACKEND") or "rope"
67
+ default_backend = (
68
+ Backend(default) if default in ["rope", "pyright"] else Backend.ROPE
69
+ )
70
+
71
+ tool_backends = {}
72
+ for tool in SHARED_TOOLS:
73
+ val = get_env(f"{tool.upper()}_BACKEND")
74
+ if val in ["rope", "pyright"]:
75
+ tool_backends[tool] = Backend(val)
76
+
77
+ return cls(
78
+ default_backend=default_backend,
79
+ tool_backends=tool_backends,
80
+ )
81
+
82
+ def get_backend_for(self, tool: str) -> Backend:
83
+ """Get the backend to use for a specific tool."""
84
+ if tool in ROPE_ONLY_TOOLS:
85
+ return Backend.ROPE
86
+ if tool in PYRIGHT_ONLY_TOOLS:
87
+ return Backend.PYRIGHT
88
+
89
+ if tool in self.tool_backends:
90
+ return self.tool_backends[tool]
91
+ return self.default_backend
92
+
93
+ def set_backend(self, backend: Backend, tool: Optional[str] = None) -> None:
94
+ """Set the backend for a tool or as default.
95
+
96
+ Args:
97
+ backend: The backend to use
98
+ tool: Optional tool name. If None, sets the default backend.
99
+ """
100
+ if tool is None:
101
+ self.default_backend = backend
102
+ elif tool in SHARED_TOOLS:
103
+ self.tool_backends[tool] = backend
104
+
105
+ def set_all_backends(self, backend: Backend) -> None:
106
+ """Set all shared tools to use the same backend."""
107
+ self.default_backend = backend
108
+ self.tool_backends.clear()
109
+
110
+
111
+ # Global config instance
112
+ _config: Optional[ServerConfig] = None
113
+
114
+
115
+ def get_config() -> ServerConfig:
116
+ """Get the global server configuration."""
117
+ global _config
118
+ if _config is None:
119
+ _config = ServerConfig.from_env()
120
+ return _config
121
+
122
+
123
+ def set_config(config: ServerConfig) -> None:
124
+ """Set the global server configuration."""
125
+ global _config
126
+ _config = config
127
+
128
+
129
+ # ============================================================================
130
+ # Python Interpreter Path Configuration
131
+ # ============================================================================
132
+
133
+
134
+ def find_pyright_python_path(workspace: str) -> Optional[str]:
135
+ """Find Python path from Pyright configuration.
136
+
137
+ Checks in order:
138
+ 1. pyrightconfig.json
139
+ 2. pyproject.toml [tool.pyright]
140
+
141
+ Args:
142
+ workspace: The workspace root directory
143
+
144
+ Returns:
145
+ Python interpreter path if found, None otherwise
146
+ """
147
+ workspace_path = Path(workspace)
148
+
149
+ # Check pyrightconfig.json
150
+ pyright_config = workspace_path / "pyrightconfig.json"
151
+ if pyright_config.exists():
152
+ try:
153
+ with open(pyright_config, "r", encoding="utf-8") as f:
154
+ config = json.load(f)
155
+ python_path = _extract_python_path_from_pyright(config, workspace_path)
156
+ if python_path:
157
+ return python_path
158
+ except (json.JSONDecodeError, OSError):
159
+ pass
160
+
161
+ # Check pyproject.toml
162
+ pyproject = workspace_path / "pyproject.toml"
163
+ if pyproject.exists():
164
+ try:
165
+ with open(pyproject, "rb") as f:
166
+ config = tomllib.load(f)
167
+ pyright_config = config.get("tool", {}).get("pyright", {})
168
+ python_path = _extract_python_path_from_pyright(
169
+ pyright_config, workspace_path
170
+ )
171
+ if python_path:
172
+ return python_path
173
+ except (tomllib.TOMLDecodeError, OSError):
174
+ pass
175
+
176
+ return None
177
+
178
+
179
+ def _extract_python_path_from_pyright(
180
+ config: dict, workspace_path: Path
181
+ ) -> Optional[str]:
182
+ """Extract Python path from Pyright config dict.
183
+
184
+ Supports:
185
+ - pythonPath: direct path to Python interpreter
186
+ - venvPath + venv: virtual environment path
187
+ """
188
+ # Direct pythonPath
189
+ if "pythonPath" in config:
190
+ python_path = Path(config["pythonPath"])
191
+ if not python_path.is_absolute():
192
+ python_path = workspace_path / python_path
193
+ if python_path.exists():
194
+ return str(python_path)
195
+
196
+ # venvPath + venv combination
197
+ venv_path = config.get("venvPath")
198
+ venv_name = config.get("venv")
199
+ if venv_path and venv_name:
200
+ venv_dir = Path(venv_path)
201
+ if not venv_dir.is_absolute():
202
+ venv_dir = workspace_path / venv_dir
203
+ venv_dir = venv_dir / venv_name
204
+
205
+ # Check for Python executable
206
+ if sys.platform == "win32":
207
+ python_exe = venv_dir / "Scripts" / "python.exe"
208
+ else:
209
+ python_exe = venv_dir / "bin" / "python"
210
+
211
+ if python_exe.exists():
212
+ return str(python_exe)
213
+
214
+ return None
215
+
216
+
217
+ def find_venv_python_path(workspace: str) -> Optional[str]:
218
+ """Find Python path from common virtual environment locations.
219
+
220
+ Checks:
221
+ - .venv/bin/python
222
+ - venv/bin/python
223
+ - env/bin/python
224
+
225
+ Args:
226
+ workspace: The workspace root directory
227
+
228
+ Returns:
229
+ Python interpreter path if found, None otherwise
230
+ """
231
+ workspace_path = Path(workspace)
232
+ venv_dirs = [".venv", "venv", "env"]
233
+
234
+ for venv_dir in venv_dirs:
235
+ if sys.platform == "win32":
236
+ python_exe = workspace_path / venv_dir / "Scripts" / "python.exe"
237
+ else:
238
+ python_exe = workspace_path / venv_dir / "bin" / "python"
239
+
240
+ if python_exe.exists():
241
+ return str(python_exe)
242
+
243
+ return None
244
+
245
+
246
+ def get_python_path_for_workspace(workspace: str) -> str:
247
+ """Get the Python interpreter path for a workspace.
248
+
249
+ Priority:
250
+ 1. Manually set path (via set_python_path tool)
251
+ 2. Pyright configuration (pyrightconfig.json or pyproject.toml)
252
+ 3. Virtual environment in workspace (.venv, venv, env)
253
+ 4. Current Python interpreter (sys.executable)
254
+
255
+ Args:
256
+ workspace: The workspace root directory
257
+
258
+ Returns:
259
+ Path to Python interpreter
260
+ """
261
+ workspace = os.path.abspath(workspace)
262
+
263
+ # Check manually set path
264
+ if workspace in _python_paths:
265
+ return _python_paths[workspace]
266
+
267
+ # Check global override
268
+ if _global_python_path:
269
+ return _global_python_path
270
+
271
+ # Check Pyright config
272
+ pyright_path = find_pyright_python_path(workspace)
273
+ if pyright_path:
274
+ return pyright_path
275
+
276
+ # Check virtual environment
277
+ venv_path = find_venv_python_path(workspace)
278
+ if venv_path:
279
+ return venv_path
280
+
281
+ # Fall back to current interpreter
282
+ return sys.executable
283
+
284
+
285
+ # Per-workspace Python paths (set via tool)
286
+ _python_paths: dict[str, str] = {}
287
+
288
+ # Global Python path override
289
+ _global_python_path: Optional[str] = None
290
+
291
+ # Active workspace for single-project mode
292
+ _active_workspace: Optional[str] = None
293
+
294
+
295
+ def set_active_workspace(workspace: str) -> str:
296
+ """Set the active workspace.
297
+
298
+ Args:
299
+ workspace: Path to the workspace root
300
+
301
+ Returns:
302
+ The absolute path to the active workspace
303
+ """
304
+ global _active_workspace
305
+ _active_workspace = os.path.abspath(workspace)
306
+ return _active_workspace
307
+
308
+
309
+ def get_active_workspace() -> Optional[str]:
310
+ """Get the currently active workspace."""
311
+ return _active_workspace
312
+
313
+
314
+ def is_file_in_workspace(file_path: str) -> bool:
315
+ """Check if a file belongs to the active workspace.
316
+
317
+ Args:
318
+ file_path: Path to the file
319
+
320
+ Returns:
321
+ True if the file is within the active workspace, False otherwise.
322
+ Returns True if no active workspace is set (backward compatibility).
323
+ """
324
+ if not _active_workspace:
325
+ return True
326
+
327
+ abs_file = os.path.abspath(file_path)
328
+ return abs_file.startswith(_active_workspace)
329
+
330
+
331
+ def validate_file_workspace(file_path: str) -> Optional[dict]:
332
+ """Validate that a file is within the active workspace.
333
+
334
+ Args:
335
+ file_path: Path to the file
336
+
337
+ Returns:
338
+ Error dict if file is outside workspace, None otherwise.
339
+ """
340
+ if not is_file_in_workspace(file_path):
341
+ return {
342
+ "error": "Context Mismatch",
343
+ "message": (
344
+ f"The file '{file_path}' is outside the active workspace '{_active_workspace}'.\n\n"
345
+ "Current Logic:\n"
346
+ "1. I only analyze files from the active project to ensure accuracy and save resources.\n"
347
+ "2. You must explicitly switch the workspace if you want to work on a different project.\n\n"
348
+ "Action Required:\n"
349
+ "Please call 'switch_workspace(path=\"...\")' with the new project root before retrying."
350
+ ),
351
+ "current_workspace": _active_workspace,
352
+ }
353
+ return None
354
+
355
+
356
+ def set_python_path(python_path: str, workspace: Optional[str] = None) -> dict:
357
+ """Set the Python interpreter path.
358
+
359
+ Args:
360
+ python_path: Path to Python interpreter
361
+ workspace: Optional workspace to set path for.
362
+ If None, sets global default.
363
+
364
+ Returns:
365
+ Dict with success status and message
366
+ """
367
+ global _global_python_path
368
+
369
+ # Validate the path exists
370
+ path = Path(python_path)
371
+ if not path.exists():
372
+ return {
373
+ "success": False,
374
+ "error": f"Python path does not exist: {python_path}",
375
+ }
376
+
377
+ # Check if it's executable
378
+ if not os.access(python_path, os.X_OK):
379
+ return {
380
+ "success": False,
381
+ "error": f"Python path is not executable: {python_path}",
382
+ }
383
+
384
+ if workspace:
385
+ workspace = os.path.abspath(workspace)
386
+ _python_paths[workspace] = python_path
387
+ return {
388
+ "success": True,
389
+ "message": f"Python path set for workspace: {workspace}",
390
+ "python_path": python_path,
391
+ "workspace": workspace,
392
+ }
393
+ else:
394
+ _global_python_path = python_path
395
+ return {
396
+ "success": True,
397
+ "message": "Global Python path set",
398
+ "python_path": python_path,
399
+ }
400
+
401
+
402
+ def get_python_path_status() -> dict:
403
+ """Get the current Python path configuration status."""
404
+ return {
405
+ "global_python_path": _global_python_path,
406
+ "workspace_python_paths": dict(_python_paths),
407
+ "current_interpreter": sys.executable,
408
+ }
@@ -0,0 +1,15 @@
1
+ """LSP client for Pyright language server."""
2
+
3
+ from .client import LspClient, get_lsp_client, close_all_clients, refresh_lsp_documents
4
+ from .types import Position, Range, Location, TextDocumentIdentifier
5
+
6
+ __all__ = [
7
+ "LspClient",
8
+ "get_lsp_client",
9
+ "close_all_clients",
10
+ "refresh_lsp_documents",
11
+ "Position",
12
+ "Range",
13
+ "Location",
14
+ "TextDocumentIdentifier",
15
+ ]