Skip to content

Commit 2923335

Browse files
committed
Updating the init and conventions config of the mcp server
Signed-off-by: S3B4SZ17 <[email protected]>
1 parent ddd9a19 commit 2923335

File tree

10 files changed

+194
-177
lines changed

10 files changed

+194
-177
lines changed

.github/workflows/publish.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
push:
66
branches:
77
- main
8+
- beta
89
paths:
910
- pyproject.toml
1011
- Dockerfile

main.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"""
44

55
import os
6-
import asyncio
6+
import signal
7+
import sys
8+
import logging
79
from dotenv import load_dotenv
810

911
# Application config loader
@@ -12,28 +14,49 @@
1214
# Register all tools so they attach to the MCP server
1315
from utils.mcp_server import run_stdio, run_http
1416

17+
# Set up logging
18+
logging.basicConfig(
19+
format="%(asctime)s-%(process)d-%(levelname)s- %(message)s",
20+
level=os.environ.get("LOGLEVEL", "ERROR"),
21+
)
22+
log = logging.getLogger(__name__)
23+
1524
# Load environment variables from .env
1625
load_dotenv()
1726

1827
app_config = get_app_config()
1928

2029

30+
def handle_signals():
31+
def signal_handler(sig, frame):
32+
log.info(f"Received signal {sig}, shutting down...")
33+
os._exit(0)
34+
35+
signal.signal(signal.SIGINT, signal_handler)
36+
signal.signal(signal.SIGTERM, signal_handler)
37+
signal.signal(signal.SIGHUP, signal_handler)
38+
39+
2140
def main():
2241
# Choose transport: "stdio" or "sse" (HTTP/SSE)
42+
handle_signals()
2343
transport = os.environ.get("MCP_TRANSPORT", app_config["mcp"]["transport"]).lower()
24-
print("""
44+
log.info("""
2545
▄▖ ▌▘ ▖ ▖▄▖▄▖ ▄▖
2646
▚ ▌▌▛▘▛▌▌▛▌ ▛▖▞▌▌ ▙▌ ▚ █▌▛▘▌▌█▌▛▘
2747
▄▌▙▌▄▌▙▌▌▙▌ ▌▝ ▌▙▖▌ ▄▌▙▖▌ ▚▘▙▖▌
2848
▄▌ ▄▌
2949
""")
3050
if transport == "stdio":
3151
# Run MCP server over STDIO (local)
32-
asyncio.run(run_stdio())
52+
run_stdio()
3353
else:
3454
# Run MCP server over streamable HTTP by default
3555
run_http()
3656

3757

3858
if __name__ == "__main__":
39-
main()
59+
try:
60+
sys.exit(main())
61+
except KeyboardInterrupt:
62+
os._exit(0)

tests/conftest.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
from fastmcp import FastMCP
77

88

9+
class MockMCP(FastMCP):
10+
"""
11+
Mock class for FastMCP
12+
"""
13+
14+
pass
15+
16+
917
def util_load_json(path):
1018
"""
1119
Utility function to load a JSON file from the given path.
@@ -42,8 +50,8 @@ def mock_ctx():
4250
Returns:
4351
Context: A mocked Context object with 'fastmcp' tags.
4452
"""
45-
fastmcp: FastMCP = FastMCP(
46-
name="Test",
53+
54+
fastmcp: MockMCP = MockMCP(
4755
tags=["sysdig", "mcp", "stdio"],
4856
)
4957
ctx = Context(fastmcp=fastmcp)

tools/events_feed/tool.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from fastmcp import Context
1515
from sysdig_client import ApiException
1616
from fastmcp.prompts.prompt import PromptMessage, TextContent
17+
from fastmcp.exceptions import ToolError
1718
from starlette.requests import Request
1819
from sysdig_client.api import SecureEventsApi
1920
from utils.sysdig.old_sysdig_api import OldSysdigApi
@@ -99,7 +100,7 @@ def tool_get_event_info(self, event_id: str, ctx: Context) -> dict:
99100
response = create_standard_response(results=raw, execution_time_ms=execution_time)
100101

101102
return response
102-
except ApiException as e:
103+
except ToolError as e:
103104
logging.error("Exception when calling SecureEventsApi->get_event_v1: %s\n" % e)
104105
raise e
105106

@@ -181,7 +182,7 @@ def tool_list_runtime_events(
181182
execution_time_ms=duration_ms,
182183
)
183184
return response
184-
except ApiException as e:
185+
except ToolError as e:
185186
log.error(f"Exception when calling SecureEventsApi->get_events_v1: {e}\n")
186187
raise e
187188

@@ -225,7 +226,7 @@ def tool_get_event_process_tree(self, ctx: Context, event_id: str) -> Dict[str,
225226
)
226227

227228
return response
228-
except ApiException as e:
229+
except ToolError as e:
229230
log.error(f"Exception when calling Sysdig Sage API to get process tree: {e}")
230231
raise e
231232

tools/inventory/tool.py

Lines changed: 64 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pydantic import Field
1010
from fastmcp.server.dependencies import get_http_request
1111
from fastmcp import Context
12+
from fastmcp.exceptions import ToolError
1213
from starlette.requests import Request
1314
from sysdig_client import ApiException
1415
from sysdig_client.api import InventoryApi
@@ -69,133 +70,67 @@ def tool_list_resources(
6970
Field(
7071
description=(
7172
"""
72-
Use the filter-query-language to filter the results.
73-
73+
Sysdig Secure query filter expression to filter inventory resources.
74+
75+
Use the resource://filter-query-language to get the expected filter expression format.
76+
7477
List of supported fields:
75-
- field: accountName
76-
Description: The account name that will be included in the results.
77-
- field: accountId
78-
Description: The account id that will be included in the results.
79-
- field: cluster
80-
Description: The kubernetes cluster that will be included in the results.
81-
- field: externalDNS
82-
Description: The external DNS that will be included in the results.
83-
- field: distribution
84-
Description: The kubernetes distribution that will be included in the results.
85-
- field: integrationName
86-
Description: The name of the integration an IaC resource belongs to.
87-
- field: labels
88-
Description: The resource labels that will be included in the results.
89-
- field: location
90-
Description: The web address of an IaC Manifest.
91-
- field: name
92-
Description: The names that will be included in the results.
93-
- field: namespace
94-
Description: The namespace that will be included in the results.
95-
- field: nodeType
96-
Description: The nodeType that will be included in the results.
97-
- field: osName
98-
Description: The operating system that will be included in the results.
99-
- field: osImage
100-
Description: The operating system image that will be included in the results.
101-
- field: organization
102-
Description: The organization that will be included in the results.
103-
- field: platform
104-
Description: The platform that will be included in the results.
105-
- field: control.accepted
106-
Description: Include (or Exclude) only resources with accepted results.
107-
Supported operators: exists and not exists.
108-
- field: policy
109-
Description: Include resources that applied the selected policies.
110-
Supported operators: in, not in, exists, not exists.
111-
- field: control.severity
112-
Description: Include resources that have violated risks in the selected severities.
113-
Supported operators: in, not in.
114-
- field: control.failed
115-
Description: Include resources that have violated the selected risks.
116-
Supported operators: in, not in, exists, not exists.
117-
- field: policy.failed
118-
Description: Include resources that failed the selected policies.
119-
Supported operators: in, not in, exists, not exists.
120-
- field: policy.passed
121-
Description: Include resources that passed the selected policies.
122-
Supported operators: in, not in, exists, not exists.
123-
- field: projectName
124-
Description: The project name that will be included in the results.
125-
- field: projectId
126-
Description: The project id that will be included in the results.
127-
- field: region
128-
Description: The regions that will be included in the results.
129-
- field: repository
130-
Description: The Repository an IaC resource belongs to.
131-
- field: resourceOrigin
132-
Description: Origin of the resource. Supported values: Code, Deployed.
133-
- field: type
134-
Description: The resource types that will be included in the results.
135-
- field: subscriptionName
136-
Description: The Azure subscription name that will be included in the results.
137-
- field: subscriptionId
138-
Description: The Azure subscription id that will be included in the results.
139-
- field: sourceType
140-
Description: The source type of an IaC resource.
141-
Supported values: YAML, Kustomize, Terraform, Helm.
142-
- field: version
143-
Description: OCP Cluster versions that will be included in the results.
144-
- field: zone
145-
Description: The zones that will be included in the results.
146-
- field: category
147-
Description: The category that will be included in the results.
148-
Supported operators: in, not in.
149-
- field: isExposed
150-
Description: Specifies whether the resource to return is exposed to the internet.
151-
Supported operators: exists and not exists.
152-
- field: validatedExposure
153-
Description: Specifies whether the resource to return is exposed to the internet and could be reach
154-
by our network exposure validator. Supported operators: exists and not exists.
155-
- field: arn
156-
Description: The AWS ARN of the resource.
157-
- field: resourceId
158-
Description: The Azure or GCP Resource Identifier of the resource.
159-
- field: container.name
160-
Description: Filters the resource by a container.
161-
- field: architecture
162-
Description: Image architecture.
163-
- field: baseOS
164-
Description: Image Base OS.
165-
- field: digest
166-
Description: Image Digest.
167-
- field: imageId
168-
Description: Image Id.
169-
- field: os
170-
Description: Image OS.
171-
- field: container.imageName
172-
Description: Image Pullstring.
173-
- field: image.registry
174-
Description: Image Registry.
175-
- field: image.tag
176-
Description: Image tag.
177-
- field: package.inUse
178-
Description: Package in use filter. Supported operators: exists and not exists.
179-
- field: package.info
180-
Description: Filters by a package using the format [packge name] - field: [version].
181-
- field: package.path
182-
Description: Filters by package path.
183-
- field: package.type
184-
Description: Package type.
185-
- field: vuln.cvssScore
186-
Description: Filter by vulnerability CVSS. Supported operators: = and >=.
187-
- field: vuln.hasExploit
188-
Description: Filters resources by the existence of vulnerabilities with exploits.
189-
Supported operators: exists and not exists.
190-
- field: vuln.hasFix
191-
Description: Filters resources by the existence of vulnerabilities with fixes.
192-
Supported operators: exists and not exists.
193-
- field: vuln.name
194-
Description: Filter by vulnerability name.
195-
- field: vuln.severity
196-
Description: Filter by vulnerability severity. Supported operators: in, not in, exists and not exists.
197-
- field: machineImage
198-
Description: Filter by host machine image.
78+
- accountName
79+
- accountId
80+
- cluster
81+
- externalDNS
82+
- distribution
83+
- integrationName
84+
- labels
85+
- location
86+
- name
87+
- namespace
88+
- nodeType
89+
- osName
90+
- osImage
91+
- organization
92+
- platform
93+
- control.accepted
94+
- policy
95+
- control.severity
96+
- control.failed
97+
- policy.failed
98+
- policy.passed
99+
- projectName
100+
- projectId
101+
- region
102+
- repository
103+
- resourceOrigin
104+
- type
105+
- subscriptionName
106+
- subscriptionId
107+
- sourceType
108+
- version
109+
- zone
110+
- category
111+
- isExposed
112+
- validatedExposure
113+
- arn
114+
- resourceId
115+
- container.name
116+
- architecture
117+
- baseOS
118+
- digest
119+
- imageId
120+
- os
121+
- container.imageName
122+
- image.registry
123+
- image.tag
124+
- package.inUse
125+
- package.info
126+
- package.path
127+
- package.type
128+
- vuln.cvssScore
129+
- vuln.hasExploit
130+
- vuln.hasFix
131+
- vuln.name
132+
- vuln.severity
133+
- machineImage
199134
"""
200135
),
201136
examples=[
@@ -205,6 +140,7 @@ def tool_list_resources(
205140
'vuln.cvssScore >= "3"',
206141
'container.name in ("sysdig-container") and not labels exists',
207142
'imageId in ("sha256:3768ff6176e29a35ce1354622977a1e5c013045cbc4f30754ef3459218be8ac")',
143+
'platform in ("GCP", "AWS", "Azure", "Kubernetes") and isExposed exists',
208144
],
209145
),
210146
] = 'platform in ("GCP", "AWS", "Azure", "Kubernetes")',
@@ -243,7 +179,7 @@ def tool_list_resources(
243179
response = create_standard_response(results=api_response, execution_time_ms=execution_time)
244180

245181
return response
246-
except ApiException as e:
182+
except ToolError as e:
247183
logging.error("Exception when calling InventoryApi->get_resources: %s\n" % e)
248184
raise e
249185

@@ -272,6 +208,6 @@ def tool_get_resource(
272208
response = create_standard_response(results=api_response, execution_time_ms=execution_time)
273209

274210
return response
275-
except ApiException as e:
211+
except ToolError as e:
276212
log.error(f"Exception when calling InventoryApi->get_resource: {e}")
277213
raise e

tools/sysdig_sage/tool.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import time
1010
from typing import Any, Dict
1111
from fastmcp import Context
12+
from fastmcp.exceptions import ToolError
1213
from utils.sysdig.old_sysdig_api import OldSysdigApi
1314
from starlette.requests import Request
1415
from fastmcp.server.dependencies import get_http_request
@@ -74,7 +75,7 @@ async def tool_sysdig_sage(self, ctx: Context, question: str) -> Dict[str, Any]:
7475
Dict: JSON-decoded response of the executed SysQL query, or an error object.
7576
7677
Raises:
77-
Exception: If the SysQL query generation or execution fails.
78+
ToolError: If the SysQL query generation or execution fails.
7879
7980
Examples:
8081
# tool_sysdig_sage(question="Match Cloud Resource affected by Critical Vulnerability")
@@ -87,8 +88,8 @@ async def tool_sysdig_sage(self, ctx: Context, question: str) -> Dict[str, Any]:
8788
old_sysdig_api = self.init_client(config_tags=ctx.fastmcp.tags)
8889
sysql_response = await old_sysdig_api.generate_sysql_query(question)
8990
if sysql_response.status > 299:
90-
raise Exception(f"Sysdig Sage returned an error: {sysql_response.status} - {sysql_response.data}")
91-
except Exception as e:
91+
raise ToolError(f"Sysdig Sage returned an error: {sysql_response.status} - {sysql_response.data}")
92+
except ToolError as e:
9293
log.error(f"Failed to generate SysQL query: {e}")
9394
raise e
9495
json_resp = sysql_response.json() if sysql_response.data else {}
@@ -107,6 +108,6 @@ async def tool_sysdig_sage(self, ctx: Context, question: str) -> Dict[str, Any]:
107108
)
108109

109110
return response
110-
except Exception as e:
111+
except ToolError as e:
111112
log.error(f"Failed to execute SysQL query: {e}")
112113
raise e

0 commit comments

Comments
 (0)