Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Test
name: Image Tests

on:
pull_request:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_image.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Test Image Build
name: Image Scan Vulns

on:
pull_request:
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ repos:
description: Format code with ruff.
entry: make fmt
language: system
stages: ["commit", "push"]
stages: ["pre-commit", "pre-push"]
- id: ruff-check
name: Ruff Check
description: Check code style with ruff.
entry: make lint
language: system
stages: ["commit", "push"]
stages: ["pre-commit", "pre-push"]
119 changes: 97 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# MCP Server

| App Test | Helm Test |
|------|---------|
| [![App Test](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/publish.yaml/badge.svg?branch=main)](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/publish.yaml) | [![Helm Test](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/helm_test.yaml/badge.svg?branch=main)](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/helm_test.yaml) |
| Image Build | Image Scanning | Image Test |
|------|---------|-----------|
| [![Image Build](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/publish.yaml/badge.svg?branch=main)](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/publish.yaml) | [![Image Scanning](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/test_image.yaml/badge.svg?branch=main)](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/test_image.yaml) | [![Image Test](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/sysdiglabs/sysdig-mcp-server/actions/workflows/test.yaml) |

---

Expand All @@ -13,16 +13,18 @@
- [Description](#description)
- [Quickstart Guide](#quickstart-guide)
- [Available Tools](#available-tools)
- [GA Tools](#ga-tools)
- [Beta Tools](#beta-tools)
- [Available Resources](#available-resources)
- [Requirements](#requirements)
- [UV Setup](#uv-setup)
- [Configuration](#configuration)
- [Running the Server](#running-the-server)
- [Docker](#docker)
- [K8s Deployment](#k8s-deployment)
- [UV](#uv)
- [Client Configuration](#client-configuration)
- [Authentication](#authentication)
- [Server Authentication](#server-authentication)
- [Sysdig API authentication](#sysdig-api-authentication)
- [URL](#url)
- [Claude Desktop App](#claude-desktop-app)
- [MCP Inspector](#mcp-inspector)
Expand Down Expand Up @@ -82,8 +84,12 @@ Get up and running with the Sysdig MCP Server quickly using our pre-built Docker
}
```

You can find a `mcp.json` file in the root of the project that you can tweak and add to your client.

## Available Tools

### GA Tools

<details>
<summary><strong>Events Feed</strong></summary>

Expand All @@ -101,6 +107,36 @@ Get up and running with the Sysdig MCP Server quickly using our pre-built Docker
| Tool Name | Description | Sample Prompt |
|-----------|-------------|----------------|
| `list_resources` | List inventory resources using filters (e.g., platform or category) | "List all exposed IAM resources in AWS" |

</details>


<details>
<summary><strong>Sysdig Sysql</strong></summary>

| Tool Name | Description | Sample Prompt |
|-----------|-------------|----------------|
| `sysdig_sysql_execute_query` | Execute a Sysdig Sysql query against the Sysdig API and return the results | "MATCH CloudResource AFFECTED_BY Vulnerability WHERE Vulnerability.severity = 'Critical' RETURN DISTINCT CloudResource, Vulnerability LIMIT 50;" |

</details>

<details>
<summary><strong>Sysdig CLI scanner</strong></summary>

| Tool Name | Description | Sample Prompt |
|-----------|-------------|----------------|
| `run_sysdig_cli_scanner` | Run the Sysdig CLI Scanner to analyze a container image or IaC files for vulnerabilities and posture and misconfigurations. | "Scan this image ubuntu:latest for vulnerabilities" |

Only in `stdio` transport mode. Make sure to have in your local $PATH the `sysdig-cli-scanner` binary; more info in the [docs](https://docs.sysdig.com/en/sysdig-secure/install-vulnerability-cli-scanner/)
</details>

### Beta Tools

<details>
<summary><strong>Inventory</strong></summary>

| Tool Name | Description | Sample Prompt |
|-----------|-------------|----------------|
| `get_resource` | Get detailed information about an inventory resource by its hash | "Get inventory details for hash abc123" |

</details>
Expand All @@ -122,22 +158,18 @@ Get up and running with the Sysdig MCP Server quickly using our pre-built Docker
</details>

<details>
<summary><strong>Sysdig Sage</strong></summary>
<summary><strong>Sysdig Sysql</strong></summary>

| Tool Name | Description | Sample Prompt |
|-----------|-------------|----------------|
| `sysdig_sysql_sage_query` | Generate and run a SysQL query using natural language | "List top 10 pods by memory usage in the last hour" |
| `sysdig_sysql_sage_query` | Get a Sysdig Sysql query through the help of Sysdig Sage based on a natural language question. | "List top 10 pods by memory usage in the last hour" |

</details>

<details>
<summary><strong>Sysdig CLI scanner</strong></summary>

| Tool Name | Description | Sample Prompt |
|-----------|-------------|----------------|
| `run_sysdig_cli_scanner` | Run the Sysdig CLI Scanner to analyze a container image or IaC files for vulnerabilities and posture and misconfigurations. | "Scan this image ubuntu:latest for vulnerabilities" |
---

</details>
> [!IMPORTANT]
> In order to enable the Beta tools, set the `SYSDIG_MCP_ENABLE_BETA_TOOLS` to `true`. Use the beta tools under your own responability since some Sysdig APIs are still on beta.

### Available Resources

Expand Down Expand Up @@ -174,7 +206,7 @@ You can also set the following variables to override the default configuration:

- `SYSDIG_MCP_TRANSPORT`: The transport protocol for the MCP Server (`stdio`, `streamable-http`, `sse`). Defaults to: `stdio`.
- `SYSDIG_MCP_MOUNT_PATH`: The URL prefix for the Streamable-http/sse deployment. Defaults to: `/sysdig-mcp-server`
- `SYSDIG_MCP_LOGLEVEL`: Log Level of the application (`DEBUG`, `INFO`, `WARNING`, `ERROR`). Defaults to: `INFO`
- `SYSDIG_MCP_LOGLEVEL`: Log Level of the application (`DEBUG`, `INFO`, `WARNING`, `ERROR`). Defaults to: `ERROR`
- `SYSDIG_MCP_LISTENING_PORT`: The port for the server when it is deployed using remote protocols (`steamable-http`, `sse`). Defaults to: `8080`
- `SYSDIG_MCP_LISTENING_HOST`: The host for the server when it is deployed using remote protocols (`steamable-http`, `sse`). Defaults to: `localhost`

Expand Down Expand Up @@ -227,22 +259,67 @@ MCP_TRANSPORT=streamable-http uv run main.py

To use the MCP server with a client like Claude or Cursor, you need to provide the server's URL and authentication details.

### Authentication
### Server Authentication

When using the `sse` or `streamable-http` transport, the server requires a Bearer token for authentication. The token is passed in the `X-Sysdig-Token` or default to `Authorization` header of the HTTP request (i.e `Bearer SYSDIG_SECURE_API_TOKEN`).
If you want to secure your MCP server with Oauth you can configure some environments variables to enable it. In this case we are going to use GitHub as the example provider but it also works for Google Oauth and the rest of the providers listed in the [authentication integrations](https://gofastmcp.com/integrations) list.

In your `.env` file you will need to set the `FASTMCP_SERVER_AUTH_...` appropriate env vars depending on your Oauth provider.

```bash
export SYSDIG_MCP_...
# Oauth config vars
export FASTMCP_SERVER_AUTH="fastmcp.server.auth.providers.github.GitHubProvider"
export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID="bv2..."
export FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET="ase2..."
export FASTMCP_SERVER_AUTH_GITHUB_BASE_URL="http://localhost:8080"
export FASTMCP_SERVER_AUTH_GITHUB_REDIRECT_PATH="/auth/callback"
```

Then if your MCP client supports the Oauth authentication method you will be able to connect to it. Here is an example client you can use to test it:

```python
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport
import asyncio
import os

async def main():
async with Client(
transport=StreamableHttpTransport(
url="http://localhost:8080/sysdig-mcp-server/mcp",
headers={"X-Sysdig-Token": f"Bearer {os.getenv("SYSDIG_MCP_API_SECURE_TOKEN")}"}
),
auth="oauth"
) as client:
print("✓ Authenticated with Oauth!")
tool_name = "list_runtime_events"
result = await client.call_tool(tool_name)
print(f"{tool_name} tool completed with status code: {result.structured_content.get('status_code')} with a total of: {result.data.get('results',{}).get('page', {}).get('total', 0)} runtime events.")

if __name__ == "__main__":
asyncio.run(main())
```

You can find the above client code here `tests/mcp_test_client.py`

More information as an example of the Github Oauth config [here](https://gofastmcp.com/integrations/github)

### Sysdig API authentication

When using the `sse` or `streamable-http` transport, the server requires the `X-Sysdig-Token` header in the HTTP request (i.e `SYSDIG_SECURE_API_TOKEN`) to authenticate to the Sysdig API. This is different than the MCP server own authentication methods.

Additionally, you can specify the Sysdig Secure host by providing the `X-Sysdig-Host` header. If this header is not present, the server will use the value from the env variable `SYSDIG_MCP_API_HOST`.

Example headers:

```
Authorization: Bearer <your_sysdig_secure_api_token>
X-Sysdig-Token: Bearer <your_sysdig_secure_api_token>
X-Sysdig-Host: <your_sysdig_host>
```

### URL

If you are running the server with the `sse` or `streamable-http` transport, the URL will be `http://<host>:<port>/sysdig-mcp-server/mcp`.
If you are running the server with the `sse` or `streamable-http` transport, the URL will be `http://<host>:<port>/sysdig-mcp-server/mcp`. You can configure the `SYSDIG_MCP_MOUNT_PATH` env variable to configure the mountpoint path, default to `/sysdig-mcp-server`

For example, if you are running the server locally on port 8080, the URL will be `http://localhost:8080/sysdig-mcp-server/mcp`.

Expand Down Expand Up @@ -318,7 +395,7 @@ For the Claude Desktop app, you can manually configure the MCP server by editing

1. Run the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) locally
2. Select the transport type and have the Sysdig MCP server running accordingly.
3. Pass the Authorization header if using "streamable-http" or the SYSDIG_SECURE_API_TOKEN env var if using "stdio"
3. Pass the `X-Sysdig-Token` authorization header if using "streamable-http" or the `SYSDIG_MCP_API_SECURE_TOKEN` env var if using "stdio"

![mcp-inspector](./docs/assets/mcp-inspector.png)

Expand Down Expand Up @@ -350,5 +427,3 @@ For the Claude Desktop app, you can manually configure the MCP server by editing
3. Have fun

![goose_results](./docs/assets/goose_results.png)


Binary file modified docs/assets/mcp-inspector.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def main():
# Choose transport: "stdio" or "sse" (HTTP/SSE)
handle_signals()
transport = app_config.transport()
log.info("""
print("""
▄▖ ▌▘ ▖ ▖▄▖▄▖ ▄▖
▚ ▌▌▛▘▛▌▌▛▌ ▛▖▞▌▌ ▙▌ ▚ █▌▛▘▌▌█▌▛▘
▄▌▙▌▄▌▙▌▌▙▌ ▌▝ ▌▙▖▌ ▄▌▙▖▌ ▚▘▙▖▌
Expand Down
24 changes: 24 additions & 0 deletions mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"mcpServers": {
"sysdig-mcp-server": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"SYSDIG_MCP_API_HOST",
"-e",
"SYSDIG_MCP_TRANSPORT",
"-e",
"SYSDIG_MCP_API_SECURE_TOKEN",
"ghcr.io/sysdiglabs/sysdig-mcp-server:latest"
],
"env": {
"SYSDIG_MCP_API_HOST": "<your_sysdig_host>",
"SYSDIG_MCP_API_SECURE_TOKEN": "<your_sysdig_secure_api_token>",
"SYSDIG_MCP_TRANSPORT": "stdio"
}
}
}
}
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[project]
name = "sysdig-mcp-server"
version = "0.2.0"
version = "0.2.1"
description = "Sysdig MCP Server"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"mcp[cli]~=1.12",
"mcp[cli]~=1.14.0",
"python-dotenv~=1.1",
"pyyaml~=6.0",
"sqlalchemy~=2.0",
Expand All @@ -14,7 +14,8 @@ dependencies = [
"dask~=2025.4",
"oauthlib~=3.2",
"fastapi~=0.116.1",
"fastmcp~=2.11",
"fastmcp @ git+https://github.com/jlowin/fastmcp@7e4aaa86969e04a05330c6d4e55f9b6c647c8756",
# "fastmcp~=2.12.0", will use this form once new releases are pushed quicker to PyPI
"requests",
]

Expand All @@ -26,6 +27,7 @@ dev-dependencies = [
"pytest-cov~=6.2",
"pytest~=8.4",
"ruff~=0.12.1",
"fastmcp @ git+https://github.com/jlowin/fastmcp@7e4aaa86969e04a05330c6d4e55f9b6c647c8756",
]

[build-system]
Expand Down
30 changes: 30 additions & 0 deletions tests/mcp_test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
This is a simple test client to verify the MCP server is running and responding to requests.
"""

from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport
import asyncio
import os


async def main():
async with Client(
transport=StreamableHttpTransport(
url="http://localhost:8080/sysdig-mcp-server/mcp",
headers={"X-Sysdig-Token": f"Bearer {os.getenv('SYSDIG_MCP_API_SECURE_TOKEN')}"},
),
auth="oauth",
) as client:
print("✓ Authenticated with Oauth!")
tool_name = "list_runtime_events"
result = await client.call_tool(tool_name)
print(
f"{tool_name} tool completed with status code: "
f"{result.structured_content.get('status_code')} with a total of: "
f"{result.data.get('results', {}).get('page', {}).get('total', 0)} runtime events."
)


if __name__ == "__main__":
asyncio.run(main())
File renamed without changes.
Loading