Skip to content
Open
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
10 changes: 5 additions & 5 deletions docs/mcp_server/resources_and_prompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Typical flow after invocation:
2. It calls `list_vendor_parsers` / `get_parser` to learn existing
conventions for the same vendor (if any).
3. It drafts a new `.conf` parser and the corresponding unit tests.
4. With your approval, it uploads the parser via `sc4s_add_parser`.
4. With your approval, it uploads the parser via `add_parser`.

### Troubleshoot SC4S workflow

Expand All @@ -72,10 +72,10 @@ Parameters:
Diagnostic steps seeded by the prompt:

1. Call `sc4s_health` to check the instance status.
2. Call `sc4s_get_env` to review the current configuration.
3. Call `sc4s_list_custom_parsers` to list deployed custom parsers.
2. Call `get_env` to review the current configuration.
3. Call `list_custom_parsers` to list deployed custom parsers.
4. Propose specific fixes.
5. Apply configuration changes via `sc4s_set_env` only after explaining
5. Apply configuration changes via `set_env` only after explaining
the reasoning.

## Combining tools, resources, and prompts
Expand All @@ -88,7 +88,7 @@ adding support for a new vendor might look like this:
2. The assistant pulls context from `sc4s://docs/creating_parsers` and
calls read-only tools (`list_vendors`, `list_vendor_parsers`,
`get_parser`) to inspect existing work.
3. After you review the draft, the assistant calls `sc4s_add_parser`
3. After you review the draft, the assistant calls `add_parser`
to deploy it.
4. If the SC4S management API rejects the parser, the change is rolled
back automatically and the assistant iterates.
Expand Down
24 changes: 12 additions & 12 deletions docs/mcp_server/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ instead of a failure inside your SC4S container.
| Tool | Description |
|---|---|
| `sc4s_health()` | Returns the health payload from the SC4S management API. Use this first when troubleshooting. |
| `sc4s_get_env()` | Reads the current `env_file` from the running SC4S instance. |
| `sc4s_set_env(env_file_content)` | Uploads a new `env_file`. The SC4S API backs up the previous file, applies the new one, and restarts `syslog-ng`. On validation failure the previous file is restored. |
| `get_env()` | Reads the current `env_file` from the running SC4S instance. |
| `set_env(env_file_content)` | Uploads a new `env_file`. The SC4S API backs up the previous file, applies the new one, and restarts `syslog-ng`. On validation failure the previous file is restored. |

### Custom parsers

| Tool | Description |
|---|---|
| `sc4s_list_custom_parsers()` | Lists all custom parsers currently deployed on the SC4S instance. |
| `sc4s_get_custom_parser(name)` | Reads the content of a deployed custom parser. |
| `sc4s_add_parser(filename, content)` | Uploads a new `.conf` parser. The `.conf` extension is added if missing. SC4S validates syntax and restarts `syslog-ng`; invalid parsers are rolled back. |
| `sc4s_delete_parser(name)` | Deletes a custom parser. SC4S re-validates the remaining configuration and restarts `syslog-ng`; if validation fails, the parser is restored. |
| `list_custom_parsers()` | Lists all custom parsers currently deployed on the SC4S instance. |
| `get_custom_parser(name)` | Reads the content of a deployed custom parser. |
| `add_parser(filename, content)` | Uploads a new `.conf` parser. The `.conf` extension is added if missing. SC4S validates syntax and restarts `syslog-ng`; invalid parsers are rolled back. |
| `delete_parser(name)` | Deletes a custom parser. SC4S re-validates the remaining configuration and restarts `syslog-ng`; if validation fails, the parser is restored. |

### Splunk metadata (`splunk_metadata.csv`)

Expand All @@ -65,9 +65,9 @@ These tools manage per-vendor/product overrides that SC4S sends to Splunk

| Tool | Description |
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `sc4s_get_splunk_metadata()` | Reads `splunk_metadata.csv` entries. Each entry is `{ key, metadata, value }`, where `metadata` is one of `index, source, sourcetype, host, sc4s_template` and `key` is a `vendor_product` identifier. |
| `sc4s_set_splunk_metadata(entries)` | Overwrites `splunk_metadata.csv` with the provided list. SC4S restarts after applying. Example entry: `{"key": "juniper_netscreen", "metadata": "index", "value": "ns_index"}`. |
| `sc4s_delete_splunk_metadata()` | Clears all Splunk metadata overrides. SC4S restarts after clearing. |
| `get_splunk_metadata()` | Reads `splunk_metadata.csv` entries. Each entry is `{ key, metadata, value }`, where `metadata` is one of `index, source, sourcetype, host, sc4s_template` and `key` is a `vendor_product` identifier. |
| `set_splunk_metadata(entries)` | Overwrites `splunk_metadata.csv` with the provided list. SC4S restarts after applying. Example entry: `{"key": "juniper_netscreen", "metadata": "index", "value": "ns_index"}`. |
| `delete_splunk_metadata()` | Clears all Splunk metadata overrides. SC4S restarts after clearing. |

### Compliance metadata (`compliance_meta_by_source`)

Expand All @@ -77,9 +77,9 @@ host, IP, or subnet matching.

| Tool | Description |
|---|---|
| `sc4s_get_compliance_overrides()` | Reads both the `.conf` filter definitions (`conf_content`) and the CSV rows (`csv_content`). |
| `sc4s_set_compliance_override(conf_content, csv_content)` | Overwrites both files. `csv_content` is a list of `{filter_name, field_name, value}` dicts, where `field_name` must be `.splunk.index`, `.splunk.source`, `.splunk.sourcetype`, or `fields.<name>`. SC4S restarts after applying. |
| `sc4s_delete_compliance_override()` | Clears both files, removing all compliance overrides. SC4S restarts after clearing. |
| `get_compliance_overrides()` | Reads both the `.conf` filter definitions (`conf_content`) and the CSV rows (`csv_content`). |
| `set_compliance_override(conf_content, csv_content)` | Overwrites both files. `csv_content` is a list of `{filter_name, field_name, value}` dicts, where `field_name` must be `.splunk.index`, `.splunk.source`, `.splunk.sourcetype`, or `fields.<name>`. SC4S restarts after applying. |
| `delete_compliance_override()` | Clears both files, removing all compliance overrides. SC4S restarts after clearing. |

Example `conf_content`:

Expand Down
6 changes: 3 additions & 3 deletions sc4s_mcp/prompts/workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ def troubleshoot_prompt(symptom: str) -> list[Message]:

## Diagnostic Steps
1. First call `sc4s_health` to check the instance status.
2. Call `sc4s_get_env` to review the current configuration.
3. Call `sc4s_list_custom_parsers` to see deployed custom parsers.
2. Call `get_env` to review the current configuration.
3. Call `list_custom_parsers` to see deployed custom parsers.
4. Based on findings, suggest specific fixes.
5. If config changes are needed, use `sc4s_set_env` to apply them.
5. If config changes are needed, use `set_env` to apply them.

Always explain your reasoning before making changes."""
),
Expand Down
40 changes: 20 additions & 20 deletions sc4s_mcp/tests/test_config_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
get_parser,
search_docs,
sc4s_health,
sc4s_set_env,
sc4s_get_env,
sc4s_add_parser,
sc4s_delete_parser,
sc4s_list_custom_parsers,
sc4s_get_custom_parser,
set_env,
get_env,
add_parser,
delete_parser,
list_custom_parsers,
get_custom_parser,
)


Expand Down Expand Up @@ -193,14 +193,14 @@ def test_sc4s_health(mock_req):


@patch("tools.configuration_tools._sc4s_request")
def test_sc4s_get_env(mock_req):
sc4s_get_env()
def test_get_env(mock_req):
get_env()
mock_req.assert_called_once_with("get", "/config/env", timeout=10)


@patch("tools.configuration_tools._sc4s_request")
def test_sc4s_set_env(mock_req):
sc4s_set_env("SC4S_VAR=value\n")
def test_set_env(mock_req):
set_env("SC4S_VAR=value\n")
mock_req.assert_called_once_with(
"post",
"/config/env",
Expand All @@ -210,8 +210,8 @@ def test_sc4s_set_env(mock_req):


@patch("tools.configuration_tools._sc4s_request")
def test_sc4s_add_parser(mock_req):
sc4s_add_parser("my_parser.conf", "block parser my_parser {}")
def test_add_parser(mock_req):
add_parser("my_parser.conf", "block parser my_parser {}")
mock_req.assert_called_once_with(
"post",
"/config/parser",
Expand All @@ -221,25 +221,25 @@ def test_sc4s_add_parser(mock_req):


@patch("tools.configuration_tools._sc4s_request")
def test_sc4s_add_parser_auto_suffix(mock_req):
sc4s_add_parser("my_parser", "content")
def test_add_parser_auto_suffix(mock_req):
add_parser("my_parser", "content")
args = mock_req.call_args
assert args[1]["files"]["file"][0] == "my_parser.conf"


@patch("tools.configuration_tools._sc4s_request")
def test_sc4s_delete_parser(mock_req):
sc4s_delete_parser("my_parser")
def test_delete_parser(mock_req):
delete_parser("my_parser")
mock_req.assert_called_once_with("delete", "/config/parser/my_parser", timeout=30)


@patch("tools.configuration_tools._sc4s_request")
def test_sc4s_list_custom_parsers(mock_req):
sc4s_list_custom_parsers()
def test_list_custom_parsers(mock_req):
list_custom_parsers()
mock_req.assert_called_once_with("get", "/config/parsers", timeout=10)


@patch("tools.configuration_tools._sc4s_request")
def test_sc4s_get_custom_parser(mock_req):
sc4s_get_custom_parser("my_parser")
def test_get_custom_parser(mock_req):
get_custom_parser("my_parser")
mock_req.assert_called_once_with("get", "/config/parser/my_parser", timeout=10)
36 changes: 18 additions & 18 deletions sc4s_mcp/tests/test_metadata_tools.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,59 @@
from unittest.mock import patch

from tools.metadata_tools import (
sc4s_delete_compliance_override,
sc4s_delete_splunk_metadata,
sc4s_get_compliance_overrides,
sc4s_get_splunk_metadata,
sc4s_set_compliance_override,
sc4s_set_splunk_metadata,
delete_compliance_override,
delete_splunk_metadata,
get_compliance_overrides,
get_splunk_metadata,
set_compliance_override,
set_splunk_metadata,
)


@patch("tools.metadata_tools.sc4s_request")
def test_sc4s_get_splunk_metadata(mock_sc4s_request):
sc4s_get_splunk_metadata()
def test_get_splunk_metadata(mock_sc4s_request):
get_splunk_metadata()

mock_sc4s_request.assert_called_once_with(
"get", "/config/metadata/splunk", timeout=30
)


@patch("tools.metadata_tools.sc4s_request")
def test_sc4s_set_splunk_metadata(mock_sc4s_request):
def test_set_splunk_metadata(mock_sc4s_request):
entries = [{"key": "juniper_netscreen", "metadata": "index", "value": "ns_index"}]
sc4s_set_splunk_metadata(entries)
set_splunk_metadata(entries)

mock_sc4s_request.assert_called_once_with(
"post", "/config/metadata/splunk", json={"entries": entries}, timeout=30
)


@patch("tools.metadata_tools.sc4s_request")
def test_sc4s_delete_splunk_metadata(mock_sc4s_request):
sc4s_delete_splunk_metadata()
def test_delete_splunk_metadata(mock_sc4s_request):
delete_splunk_metadata()

mock_sc4s_request.assert_called_once_with(
"delete", "/config/metadata/splunk", timeout=30
)


@patch("tools.metadata_tools.sc4s_request")
def test_sc4s_get_compliance_overrides(mock_sc4s_request):
sc4s_get_compliance_overrides()
def test_get_compliance_overrides(mock_sc4s_request):
get_compliance_overrides()

mock_sc4s_request.assert_called_once_with(
"get", "/config/metadata/compliance", timeout=10
)


@patch("tools.metadata_tools.sc4s_request")
def test_sc4s_set_compliance_override(mock_sc4s_request):
def test_set_compliance_override(mock_sc4s_request):
conf = 'filter f_pci_zone { host("pci-*" type(glob)) };'
csv = [
{"filter_name": "f_pci_zone", "field_name": ".splunk.index", "value": "pci_idx"}
]
sc4s_set_compliance_override(conf, csv)
set_compliance_override(conf, csv)

mock_sc4s_request.assert_called_once_with(
"post",
Expand All @@ -64,8 +64,8 @@ def test_sc4s_set_compliance_override(mock_sc4s_request):


@patch("tools.metadata_tools.sc4s_request")
def test_sc4s_delete_compliance_override(mock_sc4s_request):
sc4s_delete_compliance_override()
def test_delete_compliance_override(mock_sc4s_request):
delete_compliance_override()

mock_sc4s_request.assert_called_once_with(
"delete", "/config/metadata/compliance", timeout=30
Expand Down
12 changes: 6 additions & 6 deletions sc4s_mcp/tools/configuration_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def sc4s_health() -> dict:


@mcp.tool
def sc4s_set_env(env_file_content: str) -> dict:
def set_env(env_file_content: str) -> dict:
"""Upload a new env_file to the running SC4S instance. Provide the full env_file content as a string. SC4S will backup the current env_file, apply the new one, and restart syslog-ng."""
return _sc4s_request(
"post",
Expand All @@ -112,13 +112,13 @@ def sc4s_set_env(env_file_content: str) -> dict:


@mcp.tool
def sc4s_get_env() -> dict:
def get_env() -> dict:
"""Read the current env_file from the running SC4S instance."""
return _sc4s_request("get", "/config/env", timeout=10)


@mcp.tool
def sc4s_add_parser(filename: str, content: str) -> dict:
def add_parser(filename: str, content: str) -> dict:
"""Upload a new parser .conf file to the running SC4S instance. SC4S will validate the syntax and restart syslog-ng. If syntax check fails, the parser is rolled back."""
if not filename.endswith(".conf"):
filename += ".conf"
Expand All @@ -131,18 +131,18 @@ def sc4s_add_parser(filename: str, content: str) -> dict:


@mcp.tool
def sc4s_delete_parser(name: str) -> dict:
def delete_parser(name: str) -> dict:
"""Delete a custom parser from the running SC4S instance. SC4S will validate the config after removal and restart syslog-ng. If validation fails, the parser is restored."""
return _sc4s_request("delete", f"/config/parser/{name}", timeout=30)


@mcp.tool
def sc4s_list_custom_parsers() -> dict:
def list_custom_parsers() -> dict:
"""List all custom parsers currently deployed on the running SC4S instance."""
return _sc4s_request("get", "/config/parsers", timeout=10)


@mcp.tool
def sc4s_get_custom_parser(name: str) -> dict:
def get_custom_parser(name: str) -> dict:
"""Read the content of a custom parser deployed on the running SC4S instance."""
return _sc4s_request("get", f"/config/parser/{name}", timeout=10)
12 changes: 6 additions & 6 deletions sc4s_mcp/tools/metadata_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


@mcp.tool
def sc4s_get_splunk_metadata() -> dict:
def get_splunk_metadata() -> dict:
"""Read Splunk metadata overrides from the running SC4S instance.
Returns entries from splunk_metadata.csv with key, metadata, and value columns.
The 'key' is a vendor_product identifier, 'metadata' is one of: index, source,
Expand All @@ -20,7 +20,7 @@ def sc4s_get_splunk_metadata() -> dict:


@mcp.tool
def sc4s_set_splunk_metadata(entries: list[dict]) -> dict:
def set_splunk_metadata(entries: list[dict]) -> dict:
"""Overwrite the splunk_metadata.csv on the running SC4S instance. Restarts SC4S.
Example: [{"key": "juniper_netscreen", "metadata": "index", "value": "ns_index"}]"""
return sc4s_request(
Expand All @@ -32,7 +32,7 @@ def sc4s_set_splunk_metadata(entries: list[dict]) -> dict:


@mcp.tool
def sc4s_delete_splunk_metadata() -> dict:
def delete_splunk_metadata() -> dict:
"""Clear the splunk_metadata.csv on the running SC4S instance. SC4S restarts after clearing."""
return sc4s_request("delete", "/config/metadata/splunk", timeout=30)

Expand All @@ -43,7 +43,7 @@ def sc4s_delete_splunk_metadata() -> dict:


@mcp.tool
def sc4s_get_compliance_overrides() -> dict:
def get_compliance_overrides() -> dict:
"""Read compliance metadata overrides from the running SC4S instance.
Returns the syslog-ng filter definitions (conf_content) and CSV rows (csv_content)
with filter_name, field_name, and value. These overrides redirect events to different
Expand All @@ -52,7 +52,7 @@ def sc4s_get_compliance_overrides() -> dict:


@mcp.tool
def sc4s_set_compliance_override(conf_content: str, csv_content: list[dict]) -> dict:
def set_compliance_override(conf_content: str, csv_content: list[dict]) -> dict:
"""Overwrite the compliance_meta_by_source conf and CSV files instance.
The provided content completely replaces both files. SC4S restarts after applying changes.

Expand All @@ -72,6 +72,6 @@ def sc4s_set_compliance_override(conf_content: str, csv_content: list[dict]) ->


@mcp.tool
def sc4s_delete_compliance_override() -> dict:
def delete_compliance_override() -> dict:
"""Clear both compliance_meta_by_source files (conf and CSV), removing all compliance overrides. SC4S restarts after clearing."""
return sc4s_request("delete", "/config/metadata/compliance", timeout=30)
Loading