Skip to content

Commit d002d31

Browse files
committed
refactor: AppConfig via dependency injection
1 parent b771d53 commit d002d31

File tree

12 files changed

+550
-297
lines changed

12 files changed

+550
-297
lines changed

main.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@
1414
# Register all tools so they attach to the MCP server
1515
from utils.mcp_server import run_stdio, run_http
1616

17+
# Load environment variables from .env
18+
load_dotenv()
19+
20+
app_config = get_app_config()
21+
1722
# Set up logging
1823
logging.basicConfig(
1924
format="%(asctime)s-%(process)d-%(levelname)s- %(message)s",
20-
level=os.environ.get("LOGLEVEL", "ERROR"),
25+
level=app_config.log_level(),
2126
)
2227
log = logging.getLogger(__name__)
2328

24-
# Load environment variables from .env
25-
load_dotenv()
2629

27-
app_config = get_app_config()
2830

2931

3032
def handle_signals():
@@ -40,7 +42,7 @@ def signal_handler(sig, frame):
4042
def main():
4143
# Choose transport: "stdio" or "sse" (HTTP/SSE)
4244
handle_signals()
43-
transport = os.environ.get("MCP_TRANSPORT", app_config["mcp"]["transport"]).lower()
45+
transport = os.environ.get("MCP_TRANSPORT", app_config.transport())
4446
log.info("""
4547
▄▖ ▌▘ ▖ ▖▄▖▄▖ ▄▖
4648
▚ ▌▌▛▘▛▌▌▛▌ ▛▖▞▌▌ ▙▌ ▚ █▌▛▘▌▌█▌▛▘

tools/cli_scanner/tool.py

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,37 @@
99
import subprocess
1010
from typing import Literal, Optional
1111

12-
from utils.app_config import get_app_config
12+
from utils.app_config import AppConfig
1313

14-
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=os.environ.get("LOGLEVEL", "ERROR"))
1514

16-
log = logging.getLogger(__name__)
17-
18-
# Load app config (expects keys: mcp.host, mcp.port, mcp.transport)
19-
app_config = get_app_config()
2015

2116

2217
class CLIScannerTool:
2318
"""
2419
A class to encapsulate the tools for interacting with the Sysdig CLI Scanner.
2520
"""
2621

27-
cmd: str = "sysdig-cli-scanner"
28-
default_args: list = [
29-
"--loglevel=err",
30-
"--apiurl=" + app_config["sysdig"]["host"],
31-
]
32-
iac_default_args: list = [
33-
"--iac",
34-
"--group-by=violation",
35-
"--recursive",
36-
]
37-
38-
exit_code_explained: str = """
39-
0: Scan evaluation "pass"
40-
1: Scan evaluation "fail"
41-
2: Invalid parameters
42-
3: Internal error
43-
"""
22+
def __init__(self, app_config: AppConfig):
23+
self.app_config = app_config
24+
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=app_config.log_level())
25+
self.log = logging.getLogger(__name__)
26+
self.cmd: str = "sysdig-cli-scanner"
27+
self.default_args: list = [
28+
"--loglevel=err",
29+
"--apiurl=" + app_config.sysdig_endpoint(),
30+
]
31+
self.iac_default_args: list = [
32+
"--iac",
33+
"--group-by=violation",
34+
"--recursive",
35+
]
36+
37+
self.exit_code_explained: str = """
38+
0: Scan evaluation "pass"
39+
1: Scan evaluation "fail"
40+
2: Invalid parameters
41+
3: Internal error
42+
"""
4443

4544
def check_sysdig_cli_installed(self) -> None:
4645
"""
@@ -49,7 +48,7 @@ def check_sysdig_cli_installed(self) -> None:
4948
try:
5049
# Attempt to run 'sysdig-cli-scanner --version' to check if it's installed
5150
result = subprocess.run([self.cmd, "--version"], capture_output=True, text=True, check=True)
52-
log.info(f"Sysdig CLI Scanner is installed: {result.stdout.strip()}")
51+
self.log.info(f"Sysdig CLI Scanner is installed: {result.stdout.strip()}")
5352
except subprocess.CalledProcessError as e:
5453
error: dict = {
5554
"error": "Sysdig CLI Scanner is not installed or not in the $PATH. Check the docs to install it here: https://docs.sysdig.com/en/sysdig-secure/install-vulnerability-cli-scanner/#deployment"
@@ -64,14 +63,14 @@ def check_env_credentials(self) -> None:
6463
EnvironmentError: If the SYSDIG_SECURE_TOKEN or SYSDIG_HOST environment variables are not set.
6564
"""
6665
sysdig_secure_token = os.environ.get("SYSDIG_SECURE_TOKEN")
67-
sysdig_host = os.environ.get("SYSDIG_HOST", app_config["sysdig"]["host"])
66+
sysdig_host = os.environ.get("SYSDIG_HOST", self.app_config.sysdig_endpoint())
6867
if not sysdig_secure_token:
69-
log.error("SYSDIG_SECURE_TOKEN environment variable is not set.")
68+
self.log.error("SYSDIG_SECURE_TOKEN environment variable is not set.")
7069
raise EnvironmentError("SYSDIG_SECURE_TOKEN environment variable is not set.")
7170
else:
7271
os.environ["SECURE_API_TOKEN"] = sysdig_secure_token # Ensure the token is set in the environment
7372
if not sysdig_host:
74-
log.error("SYSDIG_HOST environment variable is not set.")
73+
self.log.error("SYSDIG_HOST environment variable is not set.")
7574
raise EnvironmentError("SYSDIG_HOST environment variable is not set.")
7675

7776
def run_sysdig_cli_scanner(
@@ -124,7 +123,7 @@ def run_sysdig_cli_scanner(
124123

125124
# Prepare the command based on the mode
126125
if mode == "iac":
127-
log.info("Running Sysdig CLI Scanner in IaC mode.")
126+
self.log.info("Running Sysdig CLI Scanner in IaC mode.")
128127
extra_iac_args = [
129128
f"--group-by={iac_group_by}",
130129
f"--severity-threshold={iac_severity_threshold}",
@@ -135,7 +134,7 @@ def run_sysdig_cli_scanner(
135134
extra_iac_args = [arg for arg in extra_iac_args if arg]
136135
cmd = [self.cmd] + self.default_args + self.iac_default_args + extra_iac_args + [path_to_scan]
137136
else:
138-
log.info("Running Sysdig CLI Scanner in vulnerability mode.")
137+
self.log.info("Running Sysdig CLI Scanner in vulnerability mode.")
139138
# Default to vulnerability mode
140139
extra_args = [
141140
"--standalone" if standalone else "",
@@ -161,9 +160,9 @@ def run_sysdig_cli_scanner(
161160
}
162161
# Handle non-zero exit codes speically exit code 1
163162
except subprocess.CalledProcessError as e:
164-
log.warning(f"Sysdig CLI Scanner returned non-zero exit code: {e.returncode}")
163+
self.log.warning(f"Sysdig CLI Scanner returned non-zero exit code: {e.returncode}")
165164
if e.returncode in [2, 3]:
166-
log.error(f"Sysdig CLI Scanner encountered an error: {e.stderr.strip()}")
165+
self.log.error(f"Sysdig CLI Scanner encountered an error: {e.stderr.strip()}")
167166
result: dict = {
168167
"error": "Error running Sysdig CLI Scanner",
169168
"exit_code": e.returncode,

tools/events_feed/tool.py

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,21 @@
2020
from fastmcp.server.dependencies import get_http_request
2121
from utils.query_helpers import create_standard_response
2222
from utils.sysdig.client_config import get_configuration
23-
from utils.app_config import get_app_config
23+
from utils.app_config import AppConfig
2424
from utils.sysdig.api import initialize_api_client
2525

26-
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=os.environ.get("LOGLEVEL", "ERROR"))
27-
log = logging.getLogger(__name__)
28-
29-
# Load app config (expects keys: mcp.host, mcp.port, mcp.transport)
30-
app_config = get_app_config()
31-
3226

3327
class EventsFeedTools:
3428
"""
3529
A class to encapsulate the tools for interacting with the Sysdig Secure Events Feed API.
3630
This class provides methods to retrieve event information and list runtime events.
3731
"""
3832

33+
def __init__(self, app_config: AppConfig):
34+
self.app_config = app_config
35+
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=self.app_config.log_level())
36+
self.log = logging.getLogger(__name__)
37+
3938
def init_client(self, old_api: bool = False) -> SecureEventsApi | OldSysdigApi:
4039
"""
4140
Initializes the SecureEventsApi client from the request state.
@@ -48,16 +47,16 @@ def init_client(self, old_api: bool = False) -> SecureEventsApi | OldSysdigApi:
4847
"""
4948
secure_events_api: SecureEventsApi = None
5049
old_sysdig_api: OldSysdigApi = None
51-
transport = os.environ.get("MCP_TRANSPORT", app_config["mcp"]["transport"]).lower()
50+
transport = self.app_config.transport()
5251
if transport in ["streamable-http", "sse"]:
5352
# Try to get the HTTP request
54-
log.debug("Attempting to get the HTTP request to initialize the Sysdig API client.")
53+
self.log.debug("Attempting to get the HTTP request to initialize the Sysdig API client.")
5554
request: Request = get_http_request()
5655
secure_events_api = request.state.api_instances["secure_events"]
5756
old_sysdig_api = request.state.api_instances["old_sysdig_api"]
5857
else:
5958
# If running in STDIO mode, we need to initialize the API client from environment variables
60-
log.debug("Running in STDIO mode, initializing the Sysdig API client from environment variables.")
59+
self.log.debug("Running in STDIO mode, initializing the Sysdig API client from environment variables.")
6160
cfg = get_configuration()
6261
api_client = initialize_api_client(cfg)
6362
secure_events_api = SecureEventsApi(api_client)
@@ -167,15 +166,15 @@ def tool_list_runtime_events(
167166
to=to_ts, var_from=from_ts, filter=filter_expr, limit=limit, cursor=cursor
168167
)
169168
duration_ms = (time.time() - start_time) * 1000
170-
log.debug(f"Execution time: {duration_ms:.2f} ms")
169+
self.log.debug(f"Execution time: {duration_ms:.2f} ms")
171170

172171
response = create_standard_response(
173172
results=api_response,
174173
execution_time_ms=duration_ms,
175174
)
176175
return response
177176
except ToolError as e:
178-
log.error(f"Exception when calling SecureEventsApi->get_events_v1: {e}\n")
177+
self.log.error(f"Exception when calling SecureEventsApi->get_events_v1: {e}\n")
179178
raise e
180179

181180
# A tool to retrieve all the process-tree information for a specific event.Add commentMore actions
@@ -199,26 +198,39 @@ def tool_get_event_process_tree(self, event_id: str) -> dict:
199198
# Get process tree
200199
tree = old_api_client.request_process_tree_trees(event_id)
201200

202-
# Parse the response
203-
branches = create_standard_response(results=branches, execution_time_ms=(time.time() - start_time) * 1000)
204-
tree = create_standard_response(results=tree, execution_time_ms=(time.time() - start_time) * 1000)
201+
# Parse the response (tolerates empty bodies)
202+
branches_std = create_standard_response(results=branches, execution_time_ms=(time.time() - start_time) * 1000)
203+
tree_std = create_standard_response(results=tree, execution_time_ms=(time.time() - start_time) * 1000)
205204

206205
execution_time = (time.time() - start_time) * 1000
207206

208-
response = (
209-
{
210-
"branches": branches.get("results", []),
211-
"tree": tree.get("results", []),
212-
"metadata": {
213-
"execution_time_ms": execution_time,
214-
"timestamp": datetime.utcnow().isoformat() + "Z",
215-
},
207+
response = {
208+
"branches": branches_std.get("results", {}),
209+
"tree": tree_std.get("results", {}),
210+
"metadata": {
211+
"execution_time_ms": execution_time,
212+
"timestamp": datetime.utcnow().isoformat() + "Z",
216213
},
217-
)
214+
}
218215

219216
return response
217+
except ApiException as e:
218+
if e.status == 404:
219+
# Process tree not available for this event
220+
return {
221+
"branches": {},
222+
"tree": {},
223+
"metadata": {
224+
"execution_time_ms": (time.time() - start_time) * 1000,
225+
"timestamp": datetime.utcnow().isoformat() + "Z",
226+
"note": "Process tree not available for this event"
227+
},
228+
}
229+
else:
230+
self.log.error(f"Exception when calling process tree API: {e}")
231+
raise ToolError(f"Failed to get process tree: {e}")
220232
except ToolError as e:
221-
log.error(f"Exception when calling Sysdig Sage API to get process tree: {e}")
233+
self.log.error(f"Exception when calling Sysdig Sage API to get process tree: {e}")
222234
raise e
223235

224236
# Prompts

tools/inventory/tool.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,29 @@
33
"""
44

55
import logging
6-
import os
76
import time
87
from typing import Annotated
98
from pydantic import Field
109
from fastmcp.server.dependencies import get_http_request
1110
from fastmcp.exceptions import ToolError
1211
from starlette.requests import Request
13-
from sysdig_client import ApiException
1412
from sysdig_client.api import InventoryApi
1513
from utils.sysdig.client_config import get_configuration
16-
from utils.app_config import get_app_config
14+
from utils.app_config import AppConfig
1715
from utils.sysdig.api import initialize_api_client
1816
from utils.query_helpers import create_standard_response
1917

20-
# Configure logging
21-
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=os.environ.get("LOGLEVEL", "ERROR"))
22-
log = logging.getLogger(__name__)
23-
24-
# Load app config (expects keys: mcp.host, mcp.port, mcp.transport)
25-
app_config = get_app_config()
26-
2718

2819
class InventoryTools:
2920
"""
3021
A class to encapsulate the tools for interacting with the Sysdig Secure Inventory API.
3122
This class provides methods to list resources and retrieve a single resource by its hash.
3223
"""
24+
def __init__(self, app_config: AppConfig):
25+
self.app_config = app_config
26+
# Configure logging
27+
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=self.app_config.log_level())
28+
self.log = logging.getLogger(__name__)
3329

3430
def init_client(self) -> InventoryApi:
3531
"""
@@ -40,15 +36,15 @@ def init_client(self) -> InventoryApi:
4036
InventoryApi: An instance of the InventoryApi client.
4137
"""
4238
inventory_api: InventoryApi = None
43-
transport = os.environ.get("MCP_TRANSPORT", app_config["mcp"]["transport"]).lower()
39+
transport = self.app_config.transport()
4440
if transport in ["streamable-http", "sse"]:
4541
# Try to get the HTTP request
46-
log.debug("Attempting to get the HTTP request to initialize the Sysdig API client.")
42+
self.log.debug("Attempting to get the HTTP request to initialize the Sysdig API client.")
4743
request: Request = get_http_request()
4844
inventory_api = request.state.api_instances["inventory"]
4945
else:
5046
# If running in STDIO mode, we need to initialize the API client from environment variables
51-
log.debug("Running in STDIO mode, initializing the Sysdig API client from environment variables.")
47+
self.log.debug("Running in STDIO mode, initializing the Sysdig API client from environment variables.")
5248
cfg = get_configuration()
5349
api_client = initialize_api_client(cfg)
5450
inventory_api = InventoryApi(api_client)
@@ -206,5 +202,5 @@ def tool_get_resource(
206202

207203
return response
208204
except ToolError as e:
209-
log.error(f"Exception when calling InventoryApi->get_resource: {e}")
205+
self.log.error(f"Exception when calling InventoryApi->get_resource: {e}")
210206
raise e

0 commit comments

Comments
 (0)