Skip to content

Commit ca74936

Browse files
authored
Updating the filter expression description for the tools (#5)
# Updating the filter expression description for the tools ## Changes * Updating the filter expression description for the tools that need it. Each API endpoint will define the supported fields in the description and examples * The common query language expressions and operators are defined now in a separate resource * Adding a default non-root user to the container definition --------- Signed-off-by: S3B4SZ17 <sebastian.zumbado@sysdig.com>
1 parent 4d5f4a4 commit ca74936

File tree

16 files changed

+329
-104
lines changed

16 files changed

+329
-104
lines changed

.github/workflows/publish.yaml

Lines changed: 8 additions & 5 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
@@ -84,20 +85,22 @@ jobs:
8485
runs-on: ubuntu-latest
8586
needs: push_to_registry
8687
steps:
87-
- name: Check out the repo
88+
- name: Check out repository
8889
uses: actions/checkout@v4
90+
with:
91+
ref: ${{ github.sha }} # required for better experience using pre-releases
92+
fetch-depth: '0' # Required due to the way Git works, without it this action won't be able to find any or the correct tags
8993

9094
- name: Get tag version
9195
id: semantic_release
92-
uses: anothrNick/github-tag-action@1.73.0
96+
uses: anothrNick/github-tag-action@1.71.0
9397
env:
9498
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
9599
DEFAULT_BUMP: "patch"
96-
TAG_CONTEXT: ${{ (github.base_ref != 'main') && 'branch' || 'repo' }}
100+
TAG_CONTEXT: 'repo'
101+
WITH_V: true
97102
PRERELEASE_SUFFIX: "beta"
98103
PRERELEASE: ${{ (github.base_ref != 'main') && 'true' || 'false' }}
99-
DRY_RUN: false
100-
INITIAL_VERSION: ${{ needs.push_to_registry.outputs.tag }}
101104

102105
- name: Summary
103106
run: |

.github/workflows/test.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ jobs:
6666
permissions:
6767
contents: write # required for creating a tag
6868
steps:
69-
- name: Check out the repo
69+
- name: Check out repository
7070
uses: actions/checkout@v4
7171
with:
72-
ref: ${{ github.head_ref }} # checkout the correct branch name
73-
fetch-depth: 0
72+
ref: ${{ github.sha }} # required for better experience using pre-releases
73+
fetch-depth: '0' # Required due to the way Git works, without it this action won't be able to find any or the correct tags
7474

7575
- name: Extract current version
7676
id: pyproject_version
@@ -80,15 +80,15 @@ jobs:
8080
8181
- name: Get tag version
8282
id: semantic_release
83-
uses: anothrNick/github-tag-action@1.73.0
83+
uses: anothrNick/github-tag-action@1.71.0
8484
env:
8585
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8686
DEFAULT_BUMP: "patch"
87-
TAG_CONTEXT: ${{ (github.base_ref != 'main') && 'branch' || 'repo' }}
87+
TAG_CONTEXT: 'repo'
88+
WITH_V: true
8889
PRERELEASE_SUFFIX: "beta"
8990
PRERELEASE: ${{ (github.base_ref != 'main') && 'true' || 'false' }}
9091
DRY_RUN: true
91-
INITIAL_VERSION: ${{ steps.pyproject_version.outputs.TAG }}
9292

9393
- name: Compare versions
9494
run: |

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ COPY --from=builder --chown=app:app /app/app_config.yaml /app
3333

3434
RUN pip install /app/sysdig_mcp_server.tar.gz
3535

36+
USER 1001:1001
37+
3638
ENTRYPOINT ["sysdig-mcp-server"]

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [Description](#description)
88
- [Quickstart Guide](#quickstart-guide)
99
- [Available Tools](#available-tools)
10+
- [Available Resources](#available-resources)
1011
- [Requirements](#requirements)
1112
- [UV Setup](#uv-setup)
1213
- [Configuration](#configuration)
@@ -124,6 +125,13 @@ Get up and running with the Sysdig MCP Server quickly using our pre-built Docker
124125

125126
</details>
126127

128+
### Available Resources
129+
130+
- Sysdig Secure Vulnerability Management Overview:
131+
- VM documentation based on the following [url](https://docs.sysdig.com/en/sysdig-secure/vulnerability-management/)
132+
- Sysdig Filter Query Language Instructions:
133+
- Sysdig Filter Query Language for different API endpoint filters
134+
127135
## Requirements
128136

129137
### UV Setup

charts/sysdig-mcp/values.yaml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,12 @@ podLabels: {}
4646
podSecurityContext: {}
4747
# fsGroup: 2000
4848

49-
securityContext: {}
50-
# capabilities:
51-
# drop:
52-
# - ALL
53-
# readOnlyRootFilesystem: true
54-
# runAsNonRoot: true
55-
# runAsUser: 1000
49+
securityContext:
50+
readOnlyRootFilesystem: false
51+
runAsNonRoot: true
52+
runAsUser: 1001
53+
runAsGroup: 1001
54+
fsGroup: 1001
5655

5756
service:
5857
type: ClusterIP

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)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "sysdig-mcp-server"
3-
version = "0.1.1"
3+
version = "0.1.2-beta.0"
44
description = "Sysdig MCP Server"
55
readme = "README.md"
66
requires-python = ">=3.12"

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: 75 additions & 8 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
@@ -18,8 +19,8 @@
1819
from utils.query_helpers import create_standard_response
1920

2021
# Configure logging
21-
log = logging.getLogger(__name__)
2222
logging.basicConfig(format="%(asctime)s-%(process)d-%(levelname)s- %(message)s", level=os.environ.get("LOGLEVEL", "ERROR"))
23+
log = logging.getLogger(__name__)
2324

2425
# Load app config (expects keys: mcp.host, mcp.port, mcp.transport)
2526
app_config = get_app_config()
@@ -69,12 +70,78 @@ def tool_list_resources(
6970
Field(
7071
description=(
7172
"""
72-
Sysdig Secure filter expression for inventory resources,
73-
base filter: platform in ("GCP", "AWS", "Azure", "Kubernetes"),
74-
Examples:
75-
not isExposed exists; category in ("IAM") and isExposed exists; category in ("IAM","Audit & Monitoring")
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+
77+
List of supported fields:
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
76134
"""
77-
)
135+
),
136+
examples=[
137+
'zone in ("zone1") and machineImage = "ami-0b22b359fdfabe1b5"',
138+
'(projectId = "1235495521" or projectId = "987654321") and vuln.severity in ("Critical")',
139+
'vuln.name in ("CVE-2023-0049")',
140+
'vuln.cvssScore >= "3"',
141+
'container.name in ("sysdig-container") and not labels exists',
142+
'imageId in ("sha256:3768ff6176e29a35ce1354622977a1e5c013045cbc4f30754ef3459218be8ac")',
143+
'platform in ("GCP", "AWS", "Azure", "Kubernetes") and isExposed exists',
144+
],
78145
),
79146
] = 'platform in ("GCP", "AWS", "Azure", "Kubernetes")',
80147
page_number: Annotated[int, Field(ge=1, description="Page number for pagination (1-based index)")] = 1,
@@ -112,7 +179,7 @@ def tool_list_resources(
112179
response = create_standard_response(results=api_response, execution_time_ms=execution_time)
113180

114181
return response
115-
except ApiException as e:
182+
except ToolError as e:
116183
logging.error("Exception when calling InventoryApi->get_resources: %s\n" % e)
117184
raise e
118185

@@ -141,6 +208,6 @@ def tool_get_resource(
141208
response = create_standard_response(results=api_response, execution_time_ms=execution_time)
142209

143210
return response
144-
except ApiException as e:
211+
except ToolError as e:
145212
log.error(f"Exception when calling InventoryApi->get_resource: {e}")
146213
raise e

0 commit comments

Comments
 (0)