Skip to content

Commit afa8338

Browse files
authored
feat: add traceback to ScanError (#136)
1 parent 7ecd5ee commit afa8338

File tree

4 files changed

+17
-6
lines changed

4 files changed

+17
-6
lines changed

src/mcp_scan/MCPScanner.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import os
44
import re
5+
import traceback
56
from collections import defaultdict
67
from collections.abc import Callable
78
from typing import Any
@@ -150,11 +151,11 @@ async def get_servers_from_path(self, path: str) -> ScanPathResult:
150151
error_msg = f"resource {path} not found" if is_direct_scan(path) else f"file {path} does not exist"
151152
logger.exception("%s: %s", error_msg, path)
152153
# This is a non failing error, so we set is_failure to False.
153-
result.error = ScanError(message=error_msg, exception=e, is_failure=False)
154+
result.error = ScanError(message=error_msg, exception=e, traceback=traceback.format_exc(), is_failure=False)
154155
except Exception as e:
155156
error_msg = f"could not scan {path}" if is_direct_scan(path) else f"could not parse file {path}"
156157
logger.exception("%s: %s", error_msg, path)
157-
result.error = ScanError(message=error_msg, exception=e, is_failure=True)
158+
result.error = ScanError(message=error_msg, exception=e, traceback=traceback.format_exc(), is_failure=True)
158159
return result
159160

160161
def check_server_changed(self, path_result: ScanPathResult) -> list[Issue]:
@@ -217,11 +218,11 @@ async def scan_server(self, server: ServerScanResult) -> ServerScanResult:
217218
except HTTPStatusError as e:
218219
error_msg = "server returned HTTP status code"
219220
logger.exception("%s: %s", error_msg, server.name)
220-
result.error = ScanError(message=error_msg, exception=e, is_failure=True)
221+
result.error = ScanError(message=error_msg, exception=e, traceback=traceback.format_exc(), is_failure=True)
221222
except Exception as e:
222223
error_msg = "could not start server"
223224
logger.exception("%s: %s", error_msg, server.name)
224-
result.error = ScanError(message=error_msg, exception=e, is_failure=True)
225+
result.error = ScanError(message=error_msg, exception=e, traceback=traceback.format_exc(), is_failure=True)
225226
await self.emit("server_scanned", result)
226227
return result
227228

src/mcp_scan/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class ScanError(BaseModel):
184184
model_config = ConfigDict(arbitrary_types_allowed=True)
185185
message: str | None = None
186186
exception: Exception | str | None = None
187+
traceback: str | None = None
187188
is_failure: bool = True
188189

189190
@field_serializer("exception")
@@ -202,6 +203,7 @@ def clone(self) -> "ScanError":
202203
return ScanError(
203204
message=self.message,
204205
exception=self.exception,
206+
traceback=self.traceback,
205207
is_failure=self.is_failure,
206208
)
207209

src/mcp_scan/verify_api.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
import os
55
import ssl
6+
import traceback
67

78
import aiohttp
89
import certifi
@@ -215,7 +216,9 @@ async def analyze_machine(
215216
logger.warning(error_text)
216217
for scan_path in scan_paths:
217218
if scan_path.servers is not None and scan_path.error is None:
218-
scan_path.error = ScanError(message=error_text, exception=e, is_failure=True)
219+
scan_path.error = ScanError(
220+
message=error_text, exception=e, traceback=traceback.format_exc(), is_failure=True
221+
)
219222
return scan_paths
220223

221224
except RuntimeError as e:
@@ -240,6 +243,7 @@ async def analyze_machine(
240243
scan_path.error = ScanError(
241244
message=f"Tried calling verification api {max_retries} times. Could not reach analysis server. Last error: {error_text}",
242245
exception=None,
246+
traceback=traceback.format_exc(),
243247
is_failure=True,
244248
)
245249
return scan_paths

tests/unit/test_control_server.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,13 @@ async def test_upload_includes_scan_error_in_payload():
158158
server = ServerScanResult(name="server1", server=StdioServer(command="echo"))
159159
scan_error_message = "something went wrong"
160160
exception_message = "could not start server"
161+
traceback = "traceback"
161162
path_result_with_error = ScanPathResult(
162163
path="/test/path",
163164
servers=[server],
164-
error=ScanError(message=scan_error_message, exception=Exception(exception_message), is_failure=True),
165+
error=ScanError(
166+
message=scan_error_message, exception=Exception(exception_message), traceback=traceback, is_failure=True
167+
),
165168
)
166169

167170
with patch("mcp_scan.upload.get_user_info") as mock_get_user_info:
@@ -199,6 +202,7 @@ async def test_upload_includes_scan_error_in_payload():
199202
assert scan_error_message in sent_result["error"].get("message")
200203
assert exception_message in sent_result["error"].get("exception")
201204
assert sent_result["error"]["is_failure"] is True
205+
assert sent_result["error"]["traceback"] == traceback
202206

203207

204208
@pytest.mark.asyncio

0 commit comments

Comments
 (0)