mcp issues

This commit is contained in:
larssand
2026-06-30 12:47:31 +02:00
parent 86065eca59
commit c76e260516
5 changed files with 55 additions and 30 deletions

View File

@@ -69,7 +69,10 @@ class GraylogAggregateSource:
self.stream = stream self.stream = stream
self.query = query or "*" self.query = query or "*"
def fetch_count(self, *, range_seconds: int = 300) -> dict[str, object]: def fetch_count(self, *, range_seconds: int = 300, probe_status: dict[str, object] | None = None) -> dict[str, object]:
if probe_status is not None:
status = dict(probe_status)
else:
try: try:
status = self.client.probe() status = self.client.probe()
except RuntimeError as exc: except RuntimeError as exc:

View File

@@ -45,7 +45,10 @@ class GraylogStreamSource:
if not isinstance(self.mapping, dict): if not isinstance(self.mapping, dict):
raise RuntimeError("invalid_graylog_field_mapping") raise RuntimeError("invalid_graylog_field_mapping")
def fetch(self, *, max_events: int = 5_000, range_seconds: int = 300) -> tuple[list[LogEvent], dict[str, object]]: def fetch(self, *, max_events: int = 5_000, range_seconds: int = 300, probe_status: dict[str, object] | None = None) -> tuple[list[LogEvent], dict[str, object]]:
if probe_status is not None:
status = dict(probe_status)
else:
try: try:
status = self.client.probe() status = self.client.probe()
except RuntimeError as exc: except RuntimeError as exc:

View File

@@ -144,6 +144,8 @@ def build_status(
fetch_mode = str(runtime_values.get("graylog_fetch_mode", "auto") or "auto") fetch_mode = str(runtime_values.get("graylog_fetch_mode", "auto") or "auto")
use_aggregate = fetch_mode == "aggregate" or (fetch_mode == "auto" and max_events_per_stream > raw_sample_events) use_aggregate = fetch_mode == "aggregate" or (fetch_mode == "auto" and max_events_per_stream > raw_sample_events)
aggregate_events_total = 0 aggregate_events_total = 0
client = GraylogMcpClient(url, token, verify_tls=verify_tls)
probe_status = client.probe()
for stream_config in stream_configs: for stream_config in stream_configs:
stream_id = str(stream_config["id"]) stream_id = str(stream_config["id"])
profile = stream_profiles.get(stream_id) profile = stream_profiles.get(stream_id)
@@ -157,10 +159,10 @@ def build_status(
stream_name = str(stream_config.get("title", "") or stream_titles.get(stream_id) or stream_id) stream_name = str(stream_config.get("title", "") or stream_titles.get(stream_id) or stream_id)
aggregate_status: dict[str, object] = {} aggregate_status: dict[str, object] = {}
if use_aggregate: if use_aggregate:
aggregate_status = GraylogAggregateSource(GraylogMcpClient(url, token, verify_tls=verify_tls), stream_id, str(runtime_values.get("graylog_query", "*"))).fetch_count(range_seconds=range_seconds) aggregate_status = GraylogAggregateSource(client, stream_id, str(runtime_values.get("graylog_query", "*"))).fetch_count(range_seconds=range_seconds, probe_status=probe_status)
aggregate_events_total += int(aggregate_status.get("aggregate_events", 0) or 0) aggregate_events_total += int(aggregate_status.get("aggregate_events", 0) or 0)
raw_limit = min(raw_sample_events, 10_000) if use_aggregate else max_events_per_stream raw_limit = min(raw_sample_events, 10_000) if use_aggregate else max_events_per_stream
stream_events, stream_status = GraylogStreamSource(GraylogMcpClient(url, token, verify_tls=verify_tls), stream_id, str(runtime_values.get("graylog_query", "*")), str(runtime_values.get("graylog_field_mapping", "")), stream_name, profile_fields).fetch(max_events=raw_limit, range_seconds=range_seconds) stream_events, stream_status = GraylogStreamSource(client, stream_id, str(runtime_values.get("graylog_query", "*")), str(runtime_values.get("graylog_field_mapping", "")), stream_name, profile_fields).fetch(max_events=raw_limit, range_seconds=range_seconds, probe_status=probe_status)
events.extend(stream_events) events.extend(stream_events)
stream_statuses.append({"stream_id": stream_id, "stream_name": stream_name, **aggregate_status, **stream_status, "raw_sample_limit": raw_limit}) stream_statuses.append({"stream_id": stream_id, "stream_name": stream_name, **aggregate_status, **stream_status, "raw_sample_limit": raw_limit})
sample_limited_streams = [item for item in stream_statuses if item.get("truncated") and use_aggregate] sample_limited_streams = [item for item in stream_statuses if item.get("truncated") and use_aggregate]

View File

@@ -8,8 +8,10 @@ class _AggregateClient:
self.responses = list(responses) self.responses = list(responses)
self.arguments = [] self.arguments = []
self.schema = schema self.schema = schema
self.probes = 0
def probe(self): def probe(self):
self.probes += 1
status = {"status": "connected"} status = {"status": "connected"}
if self.schema: if self.schema:
status["tool_schemas"] = {"aggregate_messages": self.schema} status["tool_schemas"] = {"aggregate_messages": self.schema}
@@ -43,6 +45,14 @@ class GraylogAggregateTests(unittest.TestCase):
self.assertEqual(status["aggregate_status"], "error") self.assertEqual(status["aggregate_status"], "error")
self.assertIn("probe_error", status["aggregate_error"]) self.assertIn("probe_error", status["aggregate_error"])
def test_uses_existing_probe_status_without_reprobing(self):
client = _AggregateClient([
{"result": {"content": [{"type": "text", "text": '{"events": 9}'}]}}
])
status = GraylogAggregateSource(client, "firewall").fetch_count(probe_status={"status": "connected"})
self.assertEqual(status["aggregate_events"], 9)
self.assertEqual(client.probes, 0)
def test_tries_fallback_argument_shape_after_tool_error(self): def test_tries_fallback_argument_shape_after_tool_error(self):
client = _AggregateClient([ client = _AggregateClient([
{"result": {"isError": True, "content": [{"type": "text", "text": "bad metrics"}]}}, {"result": {"isError": True, "content": [{"type": "text", "text": "bad metrics"}]}},

View File

@@ -6,8 +6,10 @@ from fgai.graylog_source import GraylogStreamSource
class _Client: class _Client:
def __init__(self): def __init__(self):
self.arguments = None self.arguments = None
self.probes = 0
def probe(self): def probe(self):
self.probes += 1
return {"status": "connected"} return {"status": "connected"}
def call_tool(self, _name, arguments): def call_tool(self, _name, arguments):
@@ -48,6 +50,11 @@ class GraylogSourceTests(unittest.TestCase):
GraylogStreamSource(client, "vpn").fetch(range_seconds=86_400) GraylogStreamSource(client, "vpn").fetch(range_seconds=86_400)
self.assertEqual(client.arguments["range_seconds"], 86_400) self.assertEqual(client.arguments["range_seconds"], 86_400)
def test_uses_existing_probe_status_without_reprobing(self):
client = _Client()
GraylogStreamSource(client, "vpn").fetch(probe_status={"status": "connected"})
self.assertEqual(client.probes, 0)
def test_marks_stream_truncated_when_max_events_reached(self): def test_marks_stream_truncated_when_max_events_reached(self):
client = _Client() client = _Client()
events, status = GraylogStreamSource(client, "vpn").fetch(max_events=2) events, status = GraylogStreamSource(client, "vpn").fetch(max_events=2)