pylance-mcp-server 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +213 -0
- package/bin/pylance-mcp.js +68 -0
- package/mcp_server/__init__.py +13 -0
- package/mcp_server/__pycache__/__init__.cpython-312.pyc +0 -0
- package/mcp_server/__pycache__/__init__.cpython-313.pyc +0 -0
- package/mcp_server/__pycache__/__init__.cpython-314.pyc +0 -0
- package/mcp_server/__pycache__/ai_features.cpython-313.pyc +0 -0
- package/mcp_server/__pycache__/api_routes.cpython-313.pyc +0 -0
- package/mcp_server/__pycache__/auth.cpython-313.pyc +0 -0
- package/mcp_server/__pycache__/cloud_sync.cpython-313.pyc +0 -0
- package/mcp_server/__pycache__/logging_db.cpython-312.pyc +0 -0
- package/mcp_server/__pycache__/logging_db.cpython-313.pyc +0 -0
- package/mcp_server/__pycache__/pylance_bridge.cpython-312.pyc +0 -0
- package/mcp_server/__pycache__/pylance_bridge.cpython-313.pyc +0 -0
- package/mcp_server/__pycache__/pylance_bridge.cpython-314.pyc +0 -0
- package/mcp_server/__pycache__/resources.cpython-312.pyc +0 -0
- package/mcp_server/__pycache__/resources.cpython-313.pyc +0 -0
- package/mcp_server/__pycache__/tools.cpython-312.pyc +0 -0
- package/mcp_server/__pycache__/tools.cpython-313.pyc +0 -0
- package/mcp_server/__pycache__/tracing.cpython-313.pyc +0 -0
- package/mcp_server/ai_features.py +274 -0
- package/mcp_server/api_routes.py +429 -0
- package/mcp_server/auth.py +275 -0
- package/mcp_server/cloud_sync.py +427 -0
- package/mcp_server/logging_db.py +403 -0
- package/mcp_server/pylance_bridge.py +579 -0
- package/mcp_server/resources.py +174 -0
- package/mcp_server/tools.py +642 -0
- package/mcp_server/tracing.py +84 -0
- package/package.json +53 -0
- package/requirements.txt +29 -0
- package/scripts/check-python.js +57 -0
- package/server.py +1228 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Resources Module
|
|
3
|
+
|
|
4
|
+
Implements all @mcp.resource decorators for file system operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PylanceResources:
|
|
15
|
+
"""Collection of MCP resources for file system operations."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, workspace_root: str):
|
|
18
|
+
"""
|
|
19
|
+
Initialize resources with workspace root.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
workspace_root: Absolute path to the workspace root directory
|
|
23
|
+
"""
|
|
24
|
+
self.workspace_root = Path(workspace_root).resolve()
|
|
25
|
+
|
|
26
|
+
def list_workspace_files(self) -> List[str]:
|
|
27
|
+
"""
|
|
28
|
+
Return every .py file in the current project recursively.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of relative file paths to Python files
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
python_files = []
|
|
35
|
+
|
|
36
|
+
# Recursively find all .py files
|
|
37
|
+
for py_file in self.workspace_root.rglob("*.py"):
|
|
38
|
+
# Skip common directories
|
|
39
|
+
skip_dirs = {
|
|
40
|
+
"__pycache__",
|
|
41
|
+
".pytest_cache",
|
|
42
|
+
".mypy_cache",
|
|
43
|
+
".tox",
|
|
44
|
+
"venv",
|
|
45
|
+
"env",
|
|
46
|
+
".venv",
|
|
47
|
+
".env",
|
|
48
|
+
"node_modules",
|
|
49
|
+
".git",
|
|
50
|
+
"build",
|
|
51
|
+
"dist",
|
|
52
|
+
"*.egg-info",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Check if any parent is in skip_dirs
|
|
56
|
+
if any(part in skip_dirs or part.endswith(".egg-info") for part in py_file.parts):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
# Get relative path
|
|
61
|
+
relative_path = py_file.relative_to(self.workspace_root)
|
|
62
|
+
python_files.append(str(relative_path))
|
|
63
|
+
except ValueError:
|
|
64
|
+
# File is outside workspace root
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
# Sort for consistent ordering
|
|
68
|
+
python_files.sort()
|
|
69
|
+
|
|
70
|
+
logger.info(f"Found {len(python_files)} Python files in workspace")
|
|
71
|
+
return python_files
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"list_workspace_files error: {e}")
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
def read_file(self, path: str) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Read file content.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
path: Relative or absolute path to the file
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
File content as string
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
file_path = Path(path)
|
|
89
|
+
|
|
90
|
+
# If relative, resolve against workspace root
|
|
91
|
+
if not file_path.is_absolute():
|
|
92
|
+
file_path = self.workspace_root / file_path
|
|
93
|
+
|
|
94
|
+
file_path = file_path.resolve()
|
|
95
|
+
|
|
96
|
+
# Validate path is within workspace
|
|
97
|
+
try:
|
|
98
|
+
file_path.relative_to(self.workspace_root)
|
|
99
|
+
except ValueError:
|
|
100
|
+
raise ValueError(f"File path {path} is outside workspace root")
|
|
101
|
+
|
|
102
|
+
if not file_path.exists():
|
|
103
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
104
|
+
|
|
105
|
+
if not file_path.is_file():
|
|
106
|
+
raise ValueError(f"Path is not a file: {path}")
|
|
107
|
+
|
|
108
|
+
content = file_path.read_text(encoding="utf-8")
|
|
109
|
+
logger.info(f"Read file: {path} ({len(content)} bytes)")
|
|
110
|
+
return content
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(f"read_file error for {path}: {e}")
|
|
114
|
+
raise
|
|
115
|
+
|
|
116
|
+
def get_workspace_structure(self) -> dict:
|
|
117
|
+
"""
|
|
118
|
+
Get the complete workspace structure.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Dictionary representing the directory tree
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
def build_tree(directory: Path) -> dict:
|
|
125
|
+
"""Recursively build directory tree."""
|
|
126
|
+
tree = {
|
|
127
|
+
"name": directory.name,
|
|
128
|
+
"type": "directory",
|
|
129
|
+
"path": str(directory.relative_to(self.workspace_root)),
|
|
130
|
+
"children": []
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
skip_dirs = {
|
|
134
|
+
"__pycache__",
|
|
135
|
+
".pytest_cache",
|
|
136
|
+
".mypy_cache",
|
|
137
|
+
".tox",
|
|
138
|
+
"venv",
|
|
139
|
+
"env",
|
|
140
|
+
".venv",
|
|
141
|
+
".env",
|
|
142
|
+
"node_modules",
|
|
143
|
+
".git",
|
|
144
|
+
"build",
|
|
145
|
+
"dist",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
for item in sorted(directory.iterdir()):
|
|
150
|
+
if item.name.startswith(".") and item.name not in [".gitignore"]:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
if item.name in skip_dirs or item.name.endswith(".egg-info"):
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
if item.is_dir():
|
|
157
|
+
tree["children"].append(build_tree(item))
|
|
158
|
+
else:
|
|
159
|
+
tree["children"].append({
|
|
160
|
+
"name": item.name,
|
|
161
|
+
"type": "file",
|
|
162
|
+
"path": str(item.relative_to(self.workspace_root)),
|
|
163
|
+
})
|
|
164
|
+
except PermissionError:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
return tree
|
|
168
|
+
|
|
169
|
+
structure = build_tree(self.workspace_root)
|
|
170
|
+
return structure
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.error(f"get_workspace_structure error: {e}")
|
|
174
|
+
return {"name": "root", "type": "directory", "children": []}
|