lucidscan 0.5.12__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.
Files changed (91) hide show
  1. lucidscan/__init__.py +12 -0
  2. lucidscan/bootstrap/__init__.py +26 -0
  3. lucidscan/bootstrap/paths.py +160 -0
  4. lucidscan/bootstrap/platform.py +111 -0
  5. lucidscan/bootstrap/validation.py +76 -0
  6. lucidscan/bootstrap/versions.py +119 -0
  7. lucidscan/cli/__init__.py +50 -0
  8. lucidscan/cli/__main__.py +8 -0
  9. lucidscan/cli/arguments.py +405 -0
  10. lucidscan/cli/commands/__init__.py +64 -0
  11. lucidscan/cli/commands/autoconfigure.py +294 -0
  12. lucidscan/cli/commands/help.py +69 -0
  13. lucidscan/cli/commands/init.py +656 -0
  14. lucidscan/cli/commands/list_scanners.py +59 -0
  15. lucidscan/cli/commands/scan.py +307 -0
  16. lucidscan/cli/commands/serve.py +142 -0
  17. lucidscan/cli/commands/status.py +84 -0
  18. lucidscan/cli/commands/validate.py +105 -0
  19. lucidscan/cli/config_bridge.py +152 -0
  20. lucidscan/cli/exit_codes.py +17 -0
  21. lucidscan/cli/runner.py +284 -0
  22. lucidscan/config/__init__.py +29 -0
  23. lucidscan/config/ignore.py +178 -0
  24. lucidscan/config/loader.py +431 -0
  25. lucidscan/config/models.py +316 -0
  26. lucidscan/config/validation.py +645 -0
  27. lucidscan/core/__init__.py +3 -0
  28. lucidscan/core/domain_runner.py +463 -0
  29. lucidscan/core/git.py +174 -0
  30. lucidscan/core/logging.py +34 -0
  31. lucidscan/core/models.py +207 -0
  32. lucidscan/core/streaming.py +340 -0
  33. lucidscan/core/subprocess_runner.py +164 -0
  34. lucidscan/detection/__init__.py +21 -0
  35. lucidscan/detection/detector.py +154 -0
  36. lucidscan/detection/frameworks.py +270 -0
  37. lucidscan/detection/languages.py +328 -0
  38. lucidscan/detection/tools.py +229 -0
  39. lucidscan/generation/__init__.py +15 -0
  40. lucidscan/generation/config_generator.py +275 -0
  41. lucidscan/generation/package_installer.py +330 -0
  42. lucidscan/mcp/__init__.py +20 -0
  43. lucidscan/mcp/formatter.py +510 -0
  44. lucidscan/mcp/server.py +297 -0
  45. lucidscan/mcp/tools.py +1049 -0
  46. lucidscan/mcp/watcher.py +237 -0
  47. lucidscan/pipeline/__init__.py +17 -0
  48. lucidscan/pipeline/executor.py +187 -0
  49. lucidscan/pipeline/parallel.py +181 -0
  50. lucidscan/plugins/__init__.py +40 -0
  51. lucidscan/plugins/coverage/__init__.py +28 -0
  52. lucidscan/plugins/coverage/base.py +160 -0
  53. lucidscan/plugins/coverage/coverage_py.py +454 -0
  54. lucidscan/plugins/coverage/istanbul.py +411 -0
  55. lucidscan/plugins/discovery.py +107 -0
  56. lucidscan/plugins/enrichers/__init__.py +61 -0
  57. lucidscan/plugins/enrichers/base.py +63 -0
  58. lucidscan/plugins/linters/__init__.py +26 -0
  59. lucidscan/plugins/linters/base.py +125 -0
  60. lucidscan/plugins/linters/biome.py +448 -0
  61. lucidscan/plugins/linters/checkstyle.py +393 -0
  62. lucidscan/plugins/linters/eslint.py +368 -0
  63. lucidscan/plugins/linters/ruff.py +498 -0
  64. lucidscan/plugins/reporters/__init__.py +45 -0
  65. lucidscan/plugins/reporters/base.py +30 -0
  66. lucidscan/plugins/reporters/json_reporter.py +79 -0
  67. lucidscan/plugins/reporters/sarif_reporter.py +303 -0
  68. lucidscan/plugins/reporters/summary_reporter.py +61 -0
  69. lucidscan/plugins/reporters/table_reporter.py +81 -0
  70. lucidscan/plugins/scanners/__init__.py +57 -0
  71. lucidscan/plugins/scanners/base.py +60 -0
  72. lucidscan/plugins/scanners/checkov.py +484 -0
  73. lucidscan/plugins/scanners/opengrep.py +464 -0
  74. lucidscan/plugins/scanners/trivy.py +492 -0
  75. lucidscan/plugins/test_runners/__init__.py +27 -0
  76. lucidscan/plugins/test_runners/base.py +111 -0
  77. lucidscan/plugins/test_runners/jest.py +381 -0
  78. lucidscan/plugins/test_runners/karma.py +481 -0
  79. lucidscan/plugins/test_runners/playwright.py +434 -0
  80. lucidscan/plugins/test_runners/pytest.py +598 -0
  81. lucidscan/plugins/type_checkers/__init__.py +27 -0
  82. lucidscan/plugins/type_checkers/base.py +106 -0
  83. lucidscan/plugins/type_checkers/mypy.py +355 -0
  84. lucidscan/plugins/type_checkers/pyright.py +313 -0
  85. lucidscan/plugins/type_checkers/typescript.py +280 -0
  86. lucidscan-0.5.12.dist-info/METADATA +242 -0
  87. lucidscan-0.5.12.dist-info/RECORD +91 -0
  88. lucidscan-0.5.12.dist-info/WHEEL +5 -0
  89. lucidscan-0.5.12.dist-info/entry_points.txt +34 -0
  90. lucidscan-0.5.12.dist-info/licenses/LICENSE +201 -0
  91. lucidscan-0.5.12.dist-info/top_level.txt +1 -0
@@ -0,0 +1,297 @@
1
+ """MCP server implementation for LucidScan.
2
+
3
+ Exposes LucidScan tools to AI agents via the Model Context Protocol.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List
10
+
11
+ from mcp.server import Server
12
+ from mcp.server.stdio import stdio_server
13
+ from mcp.types import Tool, TextContent
14
+
15
+ from lucidscan.config import LucidScanConfig
16
+ from lucidscan.core.logging import get_logger
17
+ from lucidscan.mcp.tools import MCPToolExecutor
18
+
19
+ LOGGER = get_logger(__name__)
20
+
21
+
22
+ class LucidScanMCPServer:
23
+ """MCP server exposing LucidScan tools to AI agents."""
24
+
25
+ def __init__(self, project_root: Path, config: LucidScanConfig):
26
+ """Initialize LucidScanMCPServer.
27
+
28
+ Args:
29
+ project_root: Project root directory.
30
+ config: LucidScan configuration.
31
+ """
32
+ self.project_root = project_root
33
+ self.config = config
34
+ self.executor = MCPToolExecutor(project_root, config)
35
+ self.server = Server("lucidscan")
36
+ self._register_tools()
37
+
38
+ def _register_tools(self):
39
+ """Register MCP tools."""
40
+
41
+ @self.server.list_tools()
42
+ async def list_tools() -> List[Tool]:
43
+ """List available tools."""
44
+ return [
45
+ Tool(
46
+ name="scan",
47
+ description=(
48
+ "Run quality checks on the codebase or specific files. "
49
+ "By default, scans only changed files (uncommitted changes). "
50
+ "Use all_files=true for full project scan. "
51
+ "Returns structured issues with fix instructions. "
52
+ "IMPORTANT OUTPUT FORMAT: After receiving results, you MUST present them as: "
53
+ "(1) Announce what you're checking before the scan runs. "
54
+ "(2) List ALL issues grouped by domain (linting, type_checking, security, etc). "
55
+ "(3) Show pass/fail status for EVERY domain that was checked. "
56
+ "(4) End with a summary table showing: total issues, count by severity, "
57
+ "status per domain, and a recommended next action. "
58
+ "Even when no issues are found, confirm which domains passed."
59
+ ),
60
+ inputSchema={
61
+ "type": "object",
62
+ "properties": {
63
+ "domains": {
64
+ "type": "array",
65
+ "items": {"type": "string"},
66
+ "description": (
67
+ "Domains to check. Options: linting, type_checking, "
68
+ "sast, sca, iac, container, testing, coverage, all"
69
+ ),
70
+ "default": ["all"],
71
+ },
72
+ "files": {
73
+ "type": "array",
74
+ "items": {"type": "string"},
75
+ "description": "Optional list of specific files to check (relative paths)",
76
+ },
77
+ "all_files": {
78
+ "type": "boolean",
79
+ "description": (
80
+ "Scan entire project instead of just changed files. "
81
+ "By default, only uncommitted changes are scanned."
82
+ ),
83
+ "default": False,
84
+ },
85
+ "fix": {
86
+ "type": "boolean",
87
+ "description": "Whether to apply auto-fixes for fixable issues",
88
+ "default": False,
89
+ },
90
+ },
91
+ },
92
+ ),
93
+ Tool(
94
+ name="check_file",
95
+ description=(
96
+ "Check a specific file and return issues with fix instructions. "
97
+ "Automatically detects the file type and runs relevant checks."
98
+ ),
99
+ inputSchema={
100
+ "type": "object",
101
+ "properties": {
102
+ "file_path": {
103
+ "type": "string",
104
+ "description": "Path to the file to check (relative to project root)",
105
+ },
106
+ },
107
+ "required": ["file_path"],
108
+ },
109
+ ),
110
+ Tool(
111
+ name="get_fix_instructions",
112
+ description=(
113
+ "Get detailed fix instructions for a specific issue. "
114
+ "Use after running scan to get more details about how to fix an issue."
115
+ ),
116
+ inputSchema={
117
+ "type": "object",
118
+ "properties": {
119
+ "issue_id": {
120
+ "type": "string",
121
+ "description": "The issue identifier from a scan result",
122
+ },
123
+ },
124
+ "required": ["issue_id"],
125
+ },
126
+ ),
127
+ Tool(
128
+ name="apply_fix",
129
+ description=(
130
+ "Apply auto-fix for a fixable issue. "
131
+ "Currently only supports linting issues."
132
+ ),
133
+ inputSchema={
134
+ "type": "object",
135
+ "properties": {
136
+ "issue_id": {
137
+ "type": "string",
138
+ "description": "The issue identifier to fix",
139
+ },
140
+ },
141
+ "required": ["issue_id"],
142
+ },
143
+ ),
144
+ Tool(
145
+ name="get_status",
146
+ description=(
147
+ "Get current LucidScan status and configuration. "
148
+ "Shows available tools, enabled domains, and cached issues."
149
+ ),
150
+ inputSchema={
151
+ "type": "object",
152
+ "properties": {},
153
+ },
154
+ ),
155
+ Tool(
156
+ name="get_help",
157
+ description=(
158
+ "Get LucidScan documentation for AI agents. "
159
+ "Returns comprehensive markdown reference for initialization, "
160
+ "configuration, CLI commands, and MCP tools."
161
+ ),
162
+ inputSchema={
163
+ "type": "object",
164
+ "properties": {},
165
+ },
166
+ ),
167
+ Tool(
168
+ name="autoconfigure",
169
+ description=(
170
+ "Get instructions for auto-configuring LucidScan for this project. "
171
+ "Returns guidance on what files to analyze and how to generate lucidscan.yml. "
172
+ "AI should then read the codebase, read the help docs via get_help(), "
173
+ "and create the configuration file."
174
+ ),
175
+ inputSchema={
176
+ "type": "object",
177
+ "properties": {},
178
+ },
179
+ ),
180
+ Tool(
181
+ name="validate_config",
182
+ description=(
183
+ "Validate a lucidscan.yml configuration file. "
184
+ "Returns validation results with errors and warnings. "
185
+ "Use after generating or modifying configuration to ensure it's valid."
186
+ ),
187
+ inputSchema={
188
+ "type": "object",
189
+ "properties": {
190
+ "config_path": {
191
+ "type": "string",
192
+ "description": (
193
+ "Path to configuration file (relative to project root). "
194
+ "If not provided, finds lucidscan.yml in project root."
195
+ ),
196
+ },
197
+ },
198
+ },
199
+ ),
200
+ ]
201
+
202
+ @self.server.call_tool()
203
+ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
204
+ """Handle tool calls."""
205
+ import json
206
+
207
+ # Get progress token from request metadata (if client requested progress)
208
+ meta = self.server.request_context.meta
209
+ progress_token = meta.progressToken if meta else None
210
+
211
+ # Create progress callback that uses proper MCP progress notifications
212
+ async def send_progress(event: Dict[str, Any]) -> None:
213
+ """Send progress event via MCP progress notification.
214
+
215
+ Uses the standard MCP progress notification mechanism which
216
+ clients (Claude/Cursor) display prominently during tool execution.
217
+ Falls back to MCP logging if progress tokens are not supported.
218
+ """
219
+ tool_name = event.get("tool", "lucidscan")
220
+ content = event.get("content", "")
221
+ message = f"[{tool_name}] {content}"
222
+
223
+ try:
224
+ session = self.server.request_context.session
225
+
226
+ if progress_token is not None:
227
+ # Use progress notifications if client requested them
228
+ await session.send_progress_notification(
229
+ progress_token=progress_token,
230
+ progress=event.get("progress", 0),
231
+ total=event.get("total"),
232
+ message=message,
233
+ )
234
+ else:
235
+ # Fall back to MCP log messages for visibility
236
+ await session.send_log_message(
237
+ level="info",
238
+ data=message,
239
+ )
240
+ except Exception as e:
241
+ LOGGER.debug(f"Failed to send progress notification: {e}")
242
+
243
+ try:
244
+ if name == "scan":
245
+ result = await self.executor.scan(
246
+ domains=arguments.get("domains", ["all"]),
247
+ files=arguments.get("files"),
248
+ all_files=arguments.get("all_files", False),
249
+ fix=arguments.get("fix", False),
250
+ on_progress=send_progress,
251
+ )
252
+ elif name == "check_file":
253
+ result = await self.executor.check_file(
254
+ file_path=arguments["file_path"],
255
+ )
256
+ elif name == "get_fix_instructions":
257
+ result = await self.executor.get_fix_instructions(
258
+ issue_id=arguments["issue_id"],
259
+ )
260
+ elif name == "apply_fix":
261
+ result = await self.executor.apply_fix(
262
+ issue_id=arguments["issue_id"],
263
+ )
264
+ elif name == "get_status":
265
+ result = await self.executor.get_status()
266
+ elif name == "get_help":
267
+ result = await self.executor.get_help()
268
+ elif name == "autoconfigure":
269
+ result = await self.executor.autoconfigure()
270
+ elif name == "validate_config":
271
+ result = await self.executor.validate_config(
272
+ config_path=arguments.get("config_path"),
273
+ )
274
+ else:
275
+ result = {"error": f"Unknown tool: {name}"}
276
+
277
+ return [TextContent(
278
+ type="text",
279
+ text=json.dumps(result, indent=2, default=str),
280
+ )]
281
+ except Exception as e:
282
+ LOGGER.error(f"Tool {name} failed: {e}")
283
+ return [TextContent(
284
+ type="text",
285
+ text=json.dumps({"error": str(e)}),
286
+ )]
287
+
288
+ async def run(self):
289
+ """Run the MCP server over stdio."""
290
+ LOGGER.info(f"LucidScan MCP server starting for {self.project_root}")
291
+
292
+ async with stdio_server() as (read_stream, write_stream):
293
+ await self.server.run(
294
+ read_stream,
295
+ write_stream,
296
+ self.server.create_initialization_options(),
297
+ )