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.
- lucidscan/__init__.py +12 -0
- lucidscan/bootstrap/__init__.py +26 -0
- lucidscan/bootstrap/paths.py +160 -0
- lucidscan/bootstrap/platform.py +111 -0
- lucidscan/bootstrap/validation.py +76 -0
- lucidscan/bootstrap/versions.py +119 -0
- lucidscan/cli/__init__.py +50 -0
- lucidscan/cli/__main__.py +8 -0
- lucidscan/cli/arguments.py +405 -0
- lucidscan/cli/commands/__init__.py +64 -0
- lucidscan/cli/commands/autoconfigure.py +294 -0
- lucidscan/cli/commands/help.py +69 -0
- lucidscan/cli/commands/init.py +656 -0
- lucidscan/cli/commands/list_scanners.py +59 -0
- lucidscan/cli/commands/scan.py +307 -0
- lucidscan/cli/commands/serve.py +142 -0
- lucidscan/cli/commands/status.py +84 -0
- lucidscan/cli/commands/validate.py +105 -0
- lucidscan/cli/config_bridge.py +152 -0
- lucidscan/cli/exit_codes.py +17 -0
- lucidscan/cli/runner.py +284 -0
- lucidscan/config/__init__.py +29 -0
- lucidscan/config/ignore.py +178 -0
- lucidscan/config/loader.py +431 -0
- lucidscan/config/models.py +316 -0
- lucidscan/config/validation.py +645 -0
- lucidscan/core/__init__.py +3 -0
- lucidscan/core/domain_runner.py +463 -0
- lucidscan/core/git.py +174 -0
- lucidscan/core/logging.py +34 -0
- lucidscan/core/models.py +207 -0
- lucidscan/core/streaming.py +340 -0
- lucidscan/core/subprocess_runner.py +164 -0
- lucidscan/detection/__init__.py +21 -0
- lucidscan/detection/detector.py +154 -0
- lucidscan/detection/frameworks.py +270 -0
- lucidscan/detection/languages.py +328 -0
- lucidscan/detection/tools.py +229 -0
- lucidscan/generation/__init__.py +15 -0
- lucidscan/generation/config_generator.py +275 -0
- lucidscan/generation/package_installer.py +330 -0
- lucidscan/mcp/__init__.py +20 -0
- lucidscan/mcp/formatter.py +510 -0
- lucidscan/mcp/server.py +297 -0
- lucidscan/mcp/tools.py +1049 -0
- lucidscan/mcp/watcher.py +237 -0
- lucidscan/pipeline/__init__.py +17 -0
- lucidscan/pipeline/executor.py +187 -0
- lucidscan/pipeline/parallel.py +181 -0
- lucidscan/plugins/__init__.py +40 -0
- lucidscan/plugins/coverage/__init__.py +28 -0
- lucidscan/plugins/coverage/base.py +160 -0
- lucidscan/plugins/coverage/coverage_py.py +454 -0
- lucidscan/plugins/coverage/istanbul.py +411 -0
- lucidscan/plugins/discovery.py +107 -0
- lucidscan/plugins/enrichers/__init__.py +61 -0
- lucidscan/plugins/enrichers/base.py +63 -0
- lucidscan/plugins/linters/__init__.py +26 -0
- lucidscan/plugins/linters/base.py +125 -0
- lucidscan/plugins/linters/biome.py +448 -0
- lucidscan/plugins/linters/checkstyle.py +393 -0
- lucidscan/plugins/linters/eslint.py +368 -0
- lucidscan/plugins/linters/ruff.py +498 -0
- lucidscan/plugins/reporters/__init__.py +45 -0
- lucidscan/plugins/reporters/base.py +30 -0
- lucidscan/plugins/reporters/json_reporter.py +79 -0
- lucidscan/plugins/reporters/sarif_reporter.py +303 -0
- lucidscan/plugins/reporters/summary_reporter.py +61 -0
- lucidscan/plugins/reporters/table_reporter.py +81 -0
- lucidscan/plugins/scanners/__init__.py +57 -0
- lucidscan/plugins/scanners/base.py +60 -0
- lucidscan/plugins/scanners/checkov.py +484 -0
- lucidscan/plugins/scanners/opengrep.py +464 -0
- lucidscan/plugins/scanners/trivy.py +492 -0
- lucidscan/plugins/test_runners/__init__.py +27 -0
- lucidscan/plugins/test_runners/base.py +111 -0
- lucidscan/plugins/test_runners/jest.py +381 -0
- lucidscan/plugins/test_runners/karma.py +481 -0
- lucidscan/plugins/test_runners/playwright.py +434 -0
- lucidscan/plugins/test_runners/pytest.py +598 -0
- lucidscan/plugins/type_checkers/__init__.py +27 -0
- lucidscan/plugins/type_checkers/base.py +106 -0
- lucidscan/plugins/type_checkers/mypy.py +355 -0
- lucidscan/plugins/type_checkers/pyright.py +313 -0
- lucidscan/plugins/type_checkers/typescript.py +280 -0
- lucidscan-0.5.12.dist-info/METADATA +242 -0
- lucidscan-0.5.12.dist-info/RECORD +91 -0
- lucidscan-0.5.12.dist-info/WHEEL +5 -0
- lucidscan-0.5.12.dist-info/entry_points.txt +34 -0
- lucidscan-0.5.12.dist-info/licenses/LICENSE +201 -0
- lucidscan-0.5.12.dist-info/top_level.txt +1 -0
lucidscan/mcp/server.py
ADDED
|
@@ -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
|
+
)
|