Skip to content

feat(clp-mcp-server): Add search_by_kql MCP tool call.#1436

Merged
LinZhihao-723 merged 47 commits into
y-scope:mainfrom
20001020ycx:10-18-substring-search-api
Oct 22, 2025
Merged

feat(clp-mcp-server): Add search_by_kql MCP tool call.#1436
LinZhihao-723 merged 47 commits into
y-scope:mainfrom
20001020ycx:10-18-substring-search-api

Conversation

@20001020ycx

@20001020ycx 20001020ycx commented Oct 17, 2025

Copy link
Copy Markdown
Contributor

Description

This PR implements search API for MCP server: search_by_kql.
The documentation of search_by_kql is listed below:

search_by_kql(string: kql_query)

Description:
Runs a search query that returns the 10 latest log messages that satisfy this KQL query (ordered by timestamp), the Agent can further access up to 1000 latest log messages that satisfy this KQL query using pagination. Pagination allows the Agent to query 10 log messages (one page) at a time using a page index. For example, the MCP agent can query log messages 100-110 using page index 10. The agent uses get_nth_page() to query a page from the search results.

Inputs:
kql_query: The query LLM wants to search for.

Outputs:

  • The 10 newest log messages that satisfy the search query with their timestamp in datestring format.
  • The pagination metadata for the query response.

Errors:

  • Error: The query was CANCELLED
  • Error: The query FAILED
  • Error: The query was KILLED
  • Error: The query job TIMED OUT

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

  1. Unit Tests: uv run --group dev pytest tests/server/test-utils.py -v.
  2. Lint Tests: uv run ruff check clp/components/clp-mcp-server && task lint:check-py.
  3. Manual Integration Tests: Built usingtask docker-images:package with docker compose. Connected Claude Desktop agent to the running MCP server service in docker compose. Manually instructed LLM to perform a substring_search_query, the retruned result is consistent with manual grep, with the correct formatted date string. You can see the end-to-end result using this link
Screenshot 2025-10-20 at 1 51 44 PM Note: AI team is actively working on the automated integration test.

Summary by CodeRabbit

  • New Features

    • CLI config file support via new --config-path option
    • KQL search tool to run queries and return log entries
  • Enhancements

    • Results post-processed: timestamps converted to ISO‑8601, sorted and formatted for display
    • New "N/A" placeholder for missing timestamps
    • Startup loads and validates configuration before server creation
  • Bug Fixes

    • Docstrings updated to document TimeoutError and clarify error annotations
  • Tests

    • Unit tests for filtering, timestamp handling and sorting

@coderabbitai

coderabbitai Bot commented Oct 17, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds CLI --config-path loading/validation into server entrypoint; passes a CLPConfig into server creation; introduces a KQL search tool wired to ClpConnector that returns paged, sorted and formatted results; adds timestamp conversion/formatting/sorting utilities and tests; updates several docstrings only.

Changes

Cohort / File(s) Summary
CLI & Configuration
components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py
Adds --config-path: Path CLI option; changes main(host, port)main(host, port, config_path) -> int; loads and validates YAML into CLPConfig via read_yaml_config_file and CLPConfig.model_validate; handles ValidationError and load failures; passes clp_config into create_mcp_server(clp_config) and returns exit code.
Server & Tooling
components/clp-mcp-server/clp_mcp_server/server/server.py
Changes create_mcp_server()create_mcp_server(clp_config: CLPConfig) and instantiates ClpConnector(clp_config); adds public tool search_by_kql(kql_query: str, ctx: Context) -> dict[str, Any] that starts a session, submits KQL via the connector, waits for completion, reads results, catches ValueError/RuntimeError/TimeoutError returning {"Error": "<message>"}, post-processes with sort_by_timestamp and format_query_results, and returns the first page via session manager.
Utilities (timestamp & formatting)
components/clp-mcp-server/clp_mcp_server/server/utils.py
Adds convert_epoch_to_date_string(epoch_ts: int) -> str (ms → ISO8601 UTC with millisecond precision), format_query_results(query_results: list[dict[str, Any]]) -> list[str] (formats entries as "timestamp: <ts>, message: <msg>", uses TIMESTAMP_NOT_AVAILABLE on conversion failure), and sort_by_timestamp(query_results: list[dict[str, Any]]) -> list[dict[str, Any]] (in-place descending sort; non-int timestamps treated as -1).
Constants
components/clp-mcp-server/clp_mcp_server/server/constants.py
Adds TIMESTAMP_NOT_AVAILABLE = "N/A".
Connector docstrings
components/clp-mcp-server/clp_mcp_server/clp_connector.py
Updates submit_query and wait_query_completion docstrings: clarifies ValueError wording, documents RuntimeError when ID retrieval fails, documents TimeoutError when waiting times out, and standardizes :raise: formatting; no signature or logic changes.
Session manager docstring
components/clp-mcp-server/clp_mcp_server/server/session_manager.py
Updates docstring in get_page_data to reference search_by_kql alongside get_nth_page; no API or behavior changes.
Tests
components/clp-mcp-server/tests/server/__init__.py, components/clp-mcp-server/tests/server/test_utils.py
Adds test package init and unit tests for format_query_results() and sort_by_timestamp() covering invalid timestamp types/values, missing fields, and an end-to-end sorting/formatting scenario verifying ISO8601 timestamp strings.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant Config
    participant Server
    participant Connector
    participant SessionMgr

    User->>CLI: run CLI with --config-path
    CLI->>Config: read_yaml_config_file(config_path)
    Config-->>CLI: CLPConfig (validated)
    CLI->>Server: create_mcp_server(clp_config)
    Server->>Connector: ClpConnector(clp_config)

    rect rgba(200,220,255,0.25)
    Note over User,Server: search_by_kql flow
    User->>Server: search_by_kql(kql_query)
    Server->>SessionMgr: start_session()
    Server->>Connector: submit_query(kql_query)
    Server->>Connector: wait_query_completion()
    alt success
        Connector-->>Server: query complete
        Server->>Connector: read_query_results()
        Server->>Server: sort_by_timestamp() → format_query_results()
        Server->>SessionMgr: get_page_data(paged results)
        Server-->>User: paged formatted results
    else error / timeout
        Connector-->>Server: Error / TimeoutError
        Server-->>User: {"Error": "<message>"}
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely describes the primary change in the pull request, namely the addition of the search_by_kql tool call to the clp-mcp-server, which aligns directly with the implementation detailed in the PR objectives and code changes.
Docstring Coverage ✅ Passed Docstring coverage is 91.67% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66fb03d and 4d83dd0.

📒 Files selected for processing (3)
  • components/clp-mcp-server/clp_mcp_server/server/server.py (3 hunks)
  • components/clp-mcp-server/clp_mcp_server/server/utils.py (1 hunks)
  • components/clp-mcp-server/tests/server/test_utils.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-20T20:19:40.504Z
Learnt from: 20001020ycx
PR: y-scope/clp#1436
File: components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py:22-27
Timestamp: 2025-10-20T20:19:40.504Z
Learning: In the clp-mcp-server project (components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py), the --config Click option should use exists=True to validate the configuration file path at option processing time, even though this requires the default path to exist on all machines.

Applied to files:

  • components/clp-mcp-server/clp_mcp_server/server/server.py
🧬 Code graph analysis (2)
components/clp-mcp-server/tests/server/test_utils.py (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • format_query_results (27-57)
  • sort_by_timestamp (60-80)
components/clp-mcp-server/clp_mcp_server/server/server.py (4)
components/clp-py-utils/clp_py_utils/clp_config.py (1)
  • CLPConfig (557-739)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (4)
  • ClpConnector (23-177)
  • submit_query (43-99)
  • wait_query_completion (121-155)
  • read_results (157-177)
components/clp-mcp-server/clp_mcp_server/server/session_manager.py (4)
  • SessionManager (157-237)
  • start (176-179)
  • cache_query_result_and_get_first_page (79-96)
  • cache_query_result_and_get_first_page (194-204)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • format_query_results (27-57)
  • sort_by_timestamp (60-80)
🪛 Ruff (0.14.1)
components/clp-mcp-server/tests/server/test_utils.py

10-23: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


24-28: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


31-40: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


41-44: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


47-59: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


60-62: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


65-104: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


106-127: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: package-image

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@20001020ycx 20001020ycx changed the title feat(clp-mcp-server): Integrate CLP-S Substring Search into MCP Server feat(clp-mcp-server): Integrate CLP-S KQL Query Search into MCP Server Oct 17, 2025
@20001020ycx 20001020ycx changed the title feat(clp-mcp-server): Integrate CLP-S KQL Query Search into MCP Server feat(clp-mcp-server): Implement search_kql_query in MCP Server Oct 20, 2025
@20001020ycx 20001020ycx changed the title feat(clp-mcp-server): Implement search_kql_query in MCP Server feat(clp-mcp-server): Implement search_kql_query tool call in MCP Server Oct 20, 2025
@20001020ycx 20001020ycx marked this pull request as ready for review October 20, 2025 19:58
@20001020ycx 20001020ycx requested a review from a team as a code owner October 20, 2025 19:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (3)

97-116: Return type mismatch: read_job_status claims QueryJobStatus but returns int

This works for equality checks (IntEnum), but it breaks type expectations and makes callers do ad‑hoc casts. Return the enum.

-    async def read_job_status(self, query_id: str) -> QueryJobStatus:
+    async def read_job_status(self, query_id: str) -> QueryJobStatus:
@@
-            status = result[0] if result else None
+            raw_status = result[0] if result else None
@@
-        if status is None:
+        if raw_status is None:
             err_msg = f"Query job with ID {query_id} not found."
             raise ValueError(err_msg)
 
-        return status
+        return QueryJobStatus(raw_status)

117-141: Minor: avoid double enum wrapping and improve unknown-state message

After the previous fix, status is already a QueryJobStatus. Adjust accordingly.

-            if status in error_states:
-                err_msg = (
-                    f"Query job with ID {query_id} ended in status {QueryJobStatus(status).name}."
-                )
+            if status in error_states:
+                err_msg = f"Query job with ID {query_id} ended in status {status.name}."
                 raise RuntimeError(err_msg)
-            if status not in waiting_states:
-                err_msg = f"Query job with ID {query_id} has unknown status {status}."
+            if status not in waiting_states:
+                err_msg = f"Query job with ID {query_id} has unknown status value: {int(status)}."
                 raise RuntimeError(err_msg)

8-9: Fix incorrect MongoDB async client import — use Motor instead of PyMongo

The import will fail at runtime. PyMongo is synchronous and does not expose AsyncMongoClient. The async client must come from Motor:

-from pymongo import AsyncMongoClient
+from motor.motor_asyncio import AsyncIOMotorClient

And update the instantiation at line 28:

-        mongo_client = AsyncMongoClient(mongo_url)
+        mongo_client = AsyncIOMotorClient(mongo_url)

Ensure motor is listed in your project dependencies.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cc49ff and 9b8ddf1.

📒 Files selected for processing (5)
  • components/clp-mcp-server/clp_mcp_server/clp_connector.py (4 hunks)
  • components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py (3 hunks)
  • components/clp-mcp-server/clp_mcp_server/server/server.py (3 hunks)
  • components/clp-mcp-server/clp_mcp_server/server/utils.py (1 hunks)
  • components/clp-mcp-server/tests/server/test-utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
components/clp-mcp-server/tests/server/test-utils.py (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • filter_query_results (41-58)
  • sort_query_results (33-38)
components/clp-mcp-server/clp_mcp_server/server/server.py (3)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (4)
  • ClpConnector (22-165)
  • submit_query (40-95)
  • wait_query_completion (117-150)
  • read_results (152-165)
components/clp-mcp-server/clp_mcp_server/server/session_manager.py (4)
  • SessionManager (156-236)
  • start (175-178)
  • cache_query_result_and_get_first_page (79-96)
  • cache_query_result_and_get_first_page (193-203)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • filter_query_results (41-58)
  • sort_query_results (33-38)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (1)
components/clp-mcp-server/clp_mcp_server/constants.py (1)
  • QueryJobStatus (18-27)
components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py (3)
components/clp-py-utils/clp_py_utils/clp_config.py (1)
  • CLPConfig (576-769)
components/clp-py-utils/clp_py_utils/core.py (1)
  • read_yaml_config_file (56-62)
components/clp-mcp-server/clp_mcp_server/server/server.py (1)
  • create_mcp_server (14-103)
🪛 Ruff (0.14.1)
components/clp-mcp-server/tests/server/test-utils.py

1-1: File components/clp-mcp-server/tests/server/test-utils.py is part of an implicit namespace package. Add an __init__.py.

(INP001)


9-40: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


42-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


61-65: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


66-68: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


71-84: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


85-89: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


92-101: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


102-105: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


130-130: No newline at end of file

Add trailing newline

(W292)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: package-image
  • GitHub Check: lint-check (ubuntu-24.04)
  • GitHub Check: rust-checks (ubuntu-24.04)
  • GitHub Check: rust-checks (ubuntu-22.04)
🔇 Additional comments (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (1)

9-31: Minor: tighten error message and docstring for convert_epoch_to_date_string

Current behaviour is good. Consider clarifying that only ints in milliseconds are accepted; floats/str will raise TypeError (as intended).

Do you want me to add a tiny docstring example and a quick unit test for negative-but-valid timestamps (pre‑1970)?

Comment thread components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py
Comment thread components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/server.py
Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py Outdated
Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated
Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated
Comment thread components/clp-mcp-server/tests/server/test-utils.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b8ddf1 and 23775b9.

📒 Files selected for processing (1)
  • components/clp-mcp-server/tests/server/__init__.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.1)
components/clp-mcp-server/tests/server/__init__.py

1-1: No newline at end of file

Add trailing newline

(W292)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: package-image
  • GitHub Check: rust-checks (ubuntu-22.04)
  • GitHub Check: rust-checks (ubuntu-24.04)
  • GitHub Check: lint-check (ubuntu-24.04)

Comment thread components/clp-mcp-server/tests/server/__init__.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/clp-mcp-server/clp_mcp_server/server/server.py (1)

14-21: Document the clp_config parameter in create_mcp_server docstring.

Apply:

 def create_mcp_server(clp_config: Any) -> FastMCP:
@@
-    :return: A configured `FastMCP` instance.
+    :param clp_config: Parsed CLP configuration object (database and results cache settings).
+    :return: A configured `FastMCP` instance.
♻️ Duplicate comments (6)
components/clp-mcp-server/tests/server/test-utils.py (4)

1-1: Confirm tests package is explicit to avoid INP001.

If missing, add components/clp-mcp-server/tests/init.py (empty) or tests/server/init.py.

#!/bin/bash
# Verify explicit package to silence INP001
fd -a 'tests/__init__.py' components/clp-mcp-server || true
fd -a 'tests/server/__init__.py' components/clp-mcp-server || true

3-3: Annotate class-level fixtures with ClassVar and import it to silence RUF012.

Apply:

-from clp_mcp_server.server.utils import filter_query_results, sort_query_results
+from typing import ClassVar
+from clp_mcp_server.server.utils import filter_query_results, sort_query_results
@@
-    RAW_LOG_ENTRIES = [
+    RAW_LOG_ENTRIES: ClassVar[list[dict]] = [
@@
-    EXPECTED_RESULTS = [
+    EXPECTED_RESULTS: ClassVar[list[str]] = [
@@
-    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY = [
+    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY: ClassVar[list[dict]] = [
@@
-    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED = [
+    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED: ClassVar[list[str]] = [
@@
-    INVALID_TYPE_ENTRIES = [
+    INVALID_TYPE_ENTRIES: ClassVar[list[dict]] = [
@@
-    INVALID_TYPE_EXPECTED = [
+    INVALID_TYPE_EXPECTED: ClassVar[list[str]] = [
@@
-    INVALID_VALUE_ENTRIES = [
+    INVALID_VALUE_ENTRIES: ClassVar[list[dict]] = [
@@
-    INVALID_VALUE_EXPECTED = [
+    INVALID_VALUE_EXPECTED: ClassVar[list[str]] = [

Also applies to: 9-105


107-113: Add a robustness test for mixed timestamp types to protect against TypeError in sorting.

Append:

+    def test_sort_with_mixed_timestamp_types(self):
+        mixed = [
+            {"timestamp": "1729267200000", "message": "str ts"},
+            {"timestamp": 1729267200000.0, "message": "float ts"},
+            {"timestamp": None, "message": "none ts"},
+            {"timestamp": 1729267200123, "message": "int ts"},
+        ]
+        # sort should not raise; entries with bad/non-int timestamps end up after valid ints
+        sorted_result = sort_query_results(mixed)
+        assert sorted_result[0]["timestamp"] == 1729267200123
+        # filtering should convert invalid timestamps to "N/A"
+        filtered = filter_query_results(sorted_result)
+        assert filtered[0].startswith("timestamp: 2024-10-18T16:00:00.123Z")

130-130: Ensure file ends with a single trailing newline to silence W292.

components/clp-mcp-server/clp_mcp_server/server/server.py (1)

75-102: Guard empty KQL input to prevent submitting blank/whitespace-only jobs.

Apply:

 @mcp.tool
 async def search_kql_query(kql_query: str, ctx: Context) -> dict[str, object]:
@@
-        try:
+        try:
+            if not kql_query or not kql_query.strip():
+                return {"Error": "Empty KQL query."}
             query_id = await connector.submit_query(kql_query)
             await connector.wait_query_completion(query_id)
             results = await connector.read_results(query_id)
         except (ValueError, RuntimeError, TimeoutError) as e:
             return {"Error": str(e)}
components/clp-mcp-server/clp_mcp_server/server/utils.py (1)

33-41: Harden sort key to avoid TypeError with mixed timestamp types and ensure bad/missing values sort last.

Apply:

-def sort_query_results(query_results: list[dict]) -> list[dict]:
+def sort_query_results(query_results: list[dict]) -> list[dict]:
@@
-    return sorted(query_results, key=lambda log_entry: log_entry.get("timestamp", 0), reverse=True)
+    def _ts_key(entry: dict) -> int:
+        ts = entry.get("timestamp")
+        try:
+            return int(ts)
+        except (TypeError, ValueError):
+            return -1  # sentinel: oldest
+    return sorted(query_results, key=_ts_key, reverse=True)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23775b9 and c82ace4.

📒 Files selected for processing (3)
  • components/clp-mcp-server/clp_mcp_server/server/server.py (3 hunks)
  • components/clp-mcp-server/clp_mcp_server/server/utils.py (1 hunks)
  • components/clp-mcp-server/tests/server/test-utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
components/clp-mcp-server/tests/server/test-utils.py (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • filter_query_results (43-61)
  • sort_query_results (33-40)
components/clp-mcp-server/clp_mcp_server/server/server.py (3)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (4)
  • ClpConnector (22-165)
  • submit_query (40-95)
  • wait_query_completion (117-150)
  • read_results (152-165)
components/clp-mcp-server/clp_mcp_server/server/session_manager.py (4)
  • SessionManager (156-236)
  • start (175-178)
  • cache_query_result_and_get_first_page (79-96)
  • cache_query_result_and_get_first_page (193-203)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • filter_query_results (43-61)
  • sort_query_results (33-40)
🪛 Ruff (0.14.1)
components/clp-mcp-server/tests/server/test-utils.py

1-1: Invalid module name: 'test-utils'

(N999)


9-40: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


42-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


61-65: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


66-68: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


71-84: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


85-89: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


92-101: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


102-105: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rust-checks (ubuntu-24.04)
  • GitHub Check: lint-check (ubuntu-24.04)
  • GitHub Check: rust-checks (ubuntu-22.04)
🔇 Additional comments (2)
components/clp-mcp-server/clp_mcp_server/server/utils.py (1)

9-31: convert_epoch_to_date_string: solid validation and UTC ISO-8601 millisecond output. LGTM.

components/clp-mcp-server/clp_mcp_server/server/server.py (1)

78-82: Docstring is accurate—no action needed.

The NUM_ITEMS_PER_PAGE constant is set to 10, which aligns precisely with the docstring's claim of "10 log messages" per page. The contract is tight and correctly documented.

Comment thread components/clp-mcp-server/clp_mcp_server/server/server.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py Outdated
Comment thread components/clp-mcp-server/tests/server/test_utils.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
components/clp-mcp-server/tests/server/__init__.py (1)

1-1: Add a trailing newline (PEP 8 / Ruff W292).

File appears to end without a newline; add a single newline at EOF.

-"""Tests for server module."""
+"""Tests for server module."""
components/clp-mcp-server/tests/server/test_utils.py (1)

107-113: Add a robustness test for mixed-type timestamps in sort.

Current tests only cover int timestamps; add a case mixing int, str, float, and missing to ensure no TypeError and deterministic ordering.

Apply:

@@
     def test_sort_and_filter_query_results(self):
         """Validates the functionality of post-processing for the query response."""
         sorted_result = sort_query_results(self.RAW_LOG_ENTRIES)
         filtered_result = filter_query_results(sorted_result)
 
         assert filtered_result == self.EXPECTED_RESULTS
+
+    def test_sort_handles_mixed_timestamp_types(self):
+        """sort_query_results should not raise on mixed timestamp types."""
+        mixed = [
+            {"timestamp": 2, "message": "int-2\n"},
+            {"timestamp": "3", "message": "str-3\n"},
+            {"timestamp": 1.0, "message": "float-1\n"},
+            {"message": "missing\n"},
+        ]
+        # Should not raise:
+        sorted_mixed = sort_query_results(mixed)
+        # After sorting, valid ints should come first (latest-to-oldest), others last.
+        assert sorted_mixed[0]["message"] == "int-2\n"
+        # Sanity: length unchanged
+        assert len(sorted_mixed) == 4
+        # Filtering should still handle invalid types as N/A without raising:
+        _ = filter_query_results(sorted_mixed)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c82ace4 and 9e452d3.

📒 Files selected for processing (2)
  • components/clp-mcp-server/tests/server/__init__.py (1 hunks)
  • components/clp-mcp-server/tests/server/test_utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
components/clp-mcp-server/tests/server/test_utils.py (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • filter_query_results (43-61)
  • sort_query_results (33-40)
🪛 Ruff (0.14.1)
components/clp-mcp-server/tests/server/test_utils.py

9-40: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


42-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


61-65: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


66-68: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


71-84: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


85-89: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


92-101: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


102-105: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (ubuntu-24.04)
  • GitHub Check: lint-check (ubuntu-24.04)

Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated
Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated
Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
components/clp-mcp-server/tests/server/test_utils.py (2)

107-113: LGTM! Test correctly validates the full pipeline.

The test properly exercises both sorting and filtering with homogeneous integer timestamps. However, as noted in previous reviews, consider adding a test case for mixed-type timestamps to ensure sort_query_results handles them without raising TypeError.


9-105: Add ClassVar annotations to class-level test data.

These class attributes should be annotated with typing.ClassVar to satisfy RUF012 and clarify they are class-level constants, not mutable instance attributes.

Based on static analysis hints.

Apply this diff:

-from clp_mcp_server.server.utils import filter_query_results, sort_query_results
+from typing import ClassVar
+from clp_mcp_server.server.utils import filter_query_results, sort_query_results

 class TestUtils:
     """Test suite for utility functions."""
     
     # Testing basic functionality.
-    RAW_LOG_ENTRIES = [
+    RAW_LOG_ENTRIES: ClassVar[list[dict]] = [
         {
             "_id": "test001",
             "timestamp": 0,
@@ -39,7 +40,7 @@
         },
     ]

-    EXPECTED_RESULTS = [
+    EXPECTED_RESULTS: ClassVar[list[str]] = [
         (
             'timestamp: 2024-10-18T16:00:00.123Z, message: '
             '{"ts":1729267200123,"pid":1234,"tid":5678,'
@@ -59,11 +60,11 @@
     ]

     # Test case: missing timestamp and message fields.
-    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY = [
+    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY: ClassVar[list[dict]] = [
         {
             "_id": "test001",
         }
     ]
-    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED = [
+    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED: ClassVar[list[str]] = [
         "timestamp: N/A, message: "
     ]

     # Test case: invalid timestamp types.
-    INVALID_TYPE_ENTRIES = [
+    INVALID_TYPE_ENTRIES: ClassVar[list[dict]] = [
         {
             "timestamp": None,
             "message": '{"message":"Log with None timestamp"}\n',
@@ -83,7 +84,7 @@
             "message": '{"message":"Log with float timestamp"}\n',
         },
     ]
-    INVALID_TYPE_EXPECTED = [
+    INVALID_TYPE_EXPECTED: ClassVar[list[str]] = [
         'timestamp: N/A, message: {"message":"Log with None timestamp"}\n',
         'timestamp: N/A, message: {"message":"Log with str timestamp"}\n',
         'timestamp: N/A, message: {"message":"Log with float timestamp"}\n',
@@ -91,7 +92,7 @@

     # Test case: invalid timestamp values.
-    INVALID_VALUE_ENTRIES = [
+    INVALID_VALUE_ENTRIES: ClassVar[list[dict]] = [
         {
             "timestamp": 9999999999999999,
             "message": '{"message":"Log with overflow timestamp"}\n',
@@ -101,7 +102,7 @@
             "message": '{"message":"Log with negative overflow timestamp"}\n',
         },
     ]
-    INVALID_VALUE_EXPECTED = [
+    INVALID_VALUE_EXPECTED: ClassVar[list[str]] = [
         'timestamp: N/A, message: {"message":"Log with overflow timestamp"}\n',
         'timestamp: N/A, message: {"message":"Log with negative overflow timestamp"}\n',
     ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e452d3 and 84321f3.

📒 Files selected for processing (1)
  • components/clp-mcp-server/tests/server/test_utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
components/clp-mcp-server/tests/server/test_utils.py (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • filter_query_results (43-61)
  • sort_query_results (33-40)
🪛 Ruff (0.14.1)
components/clp-mcp-server/tests/server/test_utils.py

9-40: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


42-58: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


61-65: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


66-68: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


71-84: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


85-89: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


92-101: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


102-105: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (1)
components/clp-mcp-server/tests/server/test_utils.py (1)

5-6: LGTM! Well-structured test suite.

The test class is appropriately named and documented.

Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)

48-49: Consider coercing message to string for robustness.

If the message field is accidentally a non-string type (e.g., int or dict), it will be interpolated directly into the formatted string, potentially producing unexpected output. Coerce it to str for defensive programming.

Apply this diff:

-        message = obj.get("message", "")
-        filtered.append(f"timestamp: {timestamp_str}, message: {message}")
+        message = str(obj.get("message", ""))
+        filtered.append(f"timestamp: {timestamp_str}, message: {message}")

Optionally, add a test case with a non-string message to verify behavior.


54-61: Guard against TypeError with mixed-type timestamps.

If any entry has a non-integer timestamp (str/float/None), Python's sort will raise TypeError when comparing mixed key types. Add a safe key function that returns valid int timestamps as-is and a sentinel value for invalid types, ensuring invalid timestamps sort last.

Apply this diff:

 def sort_query_results(query_results: list[dict]) -> list[dict]:
     """
     :param query_results: A list of dictionary containing log entries with its metadata read from
     MongoDB.
     :return: A sorted list of dictionary containing log entries with its metadata, ordered by epoch
     from latest to oldest.
     """
-    return sorted(query_results, key=lambda log_entry: log_entry.get("timestamp", 0), reverse=True)
+    def _sort_key(log_entry: dict) -> int:
+        ts = log_entry.get("timestamp")
+        return ts if isinstance(ts, int) else -1  # Non-ints sort last
+
+    return sorted(query_results, key=_sort_key, reverse=True)

This aligns with filter_query_results, which treats non-int timestamps as invalid ("N/A").

components/clp-mcp-server/tests/server/test_utils.py (1)

3-106: Annotate class fixtures with ClassVar and add blank line after docstring.

All class-level fixture attributes trigger RUF012 and should be annotated with ClassVar to indicate they are class constants, not mutable instance attributes. Additionally, insert one blank line after the class docstring per D204.

Based on static analysis hints.

Apply this diff:

-from clp_mcp_server.server.utils import filter_query_results, sort_query_results
+from typing import ClassVar
+
+from clp_mcp_server.server.utils import filter_query_results, sort_query_results

 class TestUtils:
     """Test suite for utility functions."""
-    
+
 
     # Test case: invalid timestamp types.
-    INVALID_TYPE_ENTRIES = [
+    INVALID_TYPE_ENTRIES: ClassVar[list[dict]] = [
         {
             "timestamp": None,
             "message": '{"message":"Log with None timestamp"}\n',
         },
         {
             "timestamp": "1729267200000",  # str instead of int
             "message": '{"message":"Log with str timestamp"}\n',
         },
         {
             "timestamp": 1729267200000.0,  # float instead of int
             "message": '{"message":"Log with float timestamp"}\n',
         },
     ]
-    INVALID_TYPE_EXPECTED = [
+    INVALID_TYPE_EXPECTED: ClassVar[list[str]] = [
         'timestamp: N/A, message: {"message":"Log with None timestamp"}\n',
         'timestamp: N/A, message: {"message":"Log with str timestamp"}\n',
         'timestamp: N/A, message: {"message":"Log with float timestamp"}\n',
     ]
 
     # Test case: invalid timestamp values.
-    INVALID_VALUE_ENTRIES = [
+    INVALID_VALUE_ENTRIES: ClassVar[list[dict]] = [
         {
             "timestamp": 9999999999999999,
             "message": '{"message":"Log with overflow timestamp"}\n',
         },
         {
             "timestamp": -9999999999999999,
             "message": '{"message":"Log with negative overflow timestamp"}\n',
         },
     ]
-    INVALID_VALUE_EXPECTED = [
+    INVALID_VALUE_EXPECTED: ClassVar[list[str]] = [
         'timestamp: N/A, message: {"message":"Log with overflow timestamp"}\n',
         'timestamp: N/A, message: {"message":"Log with negative overflow timestamp"}\n',
     ]
 
     # Test case: missing timestamp and message fields.
-    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY = [
+    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY: ClassVar[list[dict]] = [
         {
             "_id": "test001",
         }
     ]
-    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED = [
+    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED: ClassVar[list[str]] = [
         "timestamp: N/A, message: "
     ]
 
     # Testing basic functionality.
-    RAW_LOG_ENTRIES = [
+    RAW_LOG_ENTRIES: ClassVar[list[dict]] = [
         {
             "_id": "test001",
             "timestamp": 0,
             "message": '{"ts":0,"pid":null,"tid":null,"message":"Log at epoch zero"}\n',
             "orig_file_path": "/var/log/app.log",
             "archive_id": "abc123",
             "log_event_ix": 100,
         },
         {
             "_id": "test002",
             "timestamp": 1729267200000,  # Oct 18, 2024 16:00:00.000 (milliseconds)
             "message": (
                 '{"ts":1729267200000,"pid":1234,"tid":5678,'
                 '"message":"Log with zero milliseconds"}\n'
             ),
             "orig_file_path": "/var/log/app.log",
             "archive_id": "abc124",
             "log_event_ix": 101,
         },
         {
             "_id": "test003",
             "timestamp": 1729267200123,  # Oct 18, 2024 16:00:00.123 (milliseconds)
             "message": (
                 '{"ts":1729267200123,"pid":1234,"tid":5678,'
                 '"message":"Log with millisecond precision"}\n'
             ),
             "orig_file_path": "/var/log/app.log",
             "archive_id": "abc125",
             "log_event_ix": 102,
         },
     ]
 
-    EXPECTED_RESULTS = [
+    EXPECTED_RESULTS: ClassVar[list[str]] = [
         (
             'timestamp: 2024-10-18T16:00:00.123Z, message: '
             '{"ts":1729267200123,"pid":1234,"tid":5678,'
             '"message":"Log with millisecond precision"}\n'
         ),
         (
             'timestamp: 2024-10-18T16:00:00.000Z, message: '
             '{"ts":1729267200000,"pid":1234,"tid":5678,'
             '"message":"Log with zero milliseconds"}\n'
         ),
         (
             'timestamp: 1970-01-01T00:00:00.000Z, message: '
             '{"ts":0,"pid":null,"tid":null,'
             '"message":"Log at epoch zero"}\n'
         ),
     ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84321f3 and 9606b72.

📒 Files selected for processing (2)
  • components/clp-mcp-server/clp_mcp_server/server/utils.py (1 hunks)
  • components/clp-mcp-server/tests/server/test_utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
components/clp-mcp-server/tests/server/test_utils.py (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • filter_query_results (33-51)
  • sort_query_results (54-61)
🪛 Ruff (0.14.1)
components/clp-mcp-server/tests/server/test_utils.py

6-6: 1 blank line required after class docstring

Insert 1 blank line after class docstring

(D204)


10-23: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


24-28: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


31-40: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


41-44: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


47-51: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


52-54: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


57-88: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


90-106: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: package-image
  • GitHub Check: lint-check (ubuntu-24.04)
  • GitHub Check: rust-checks (ubuntu-22.04)
  • GitHub Check: rust-checks (ubuntu-24.04)
🔇 Additional comments (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (1)

9-30: LGTM!

The timestamp conversion function correctly validates input types, handles conversion errors gracefully, and produces ISO 8601 formatted strings with millisecond precision.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
components/clp-mcp-server/clp_mcp_server/server/utils.py (1)

48-49: Consider explicit string coercion for defensive coding.

While Python's f-string formatting implicitly converts non-string types, explicitly coercing the message field ensures consistent behaviour if unexpected types (e.g., dict, list) appear in the data.

Apply this diff if you prefer explicit type safety:

         message = obj.get("message", "")
-        filtered.append(f"timestamp: {timestamp_str}, message: {message}")
+        filtered.append(f"timestamp: {timestamp_str}, message: {str(message)}")
components/clp-mcp-server/tests/server/test_utils.py (2)

3-3: Add ClassVar import to satisfy static analysis.

The class-level test data fixtures should be annotated with ClassVar to indicate they are class variables, not mutable instance attributes.

Based on static analysis hints.

Apply this diff:

+from typing import ClassVar
+
 from clp_mcp_server.server.utils import filter_query_results, sort_query_results

9-119: Annotate class-level fixtures with ClassVar.

All class-level test data fixtures should be annotated with ClassVar to indicate they are class variables, not mutable instance attributes. This resolves the RUF012 warnings from static analysis.

Based on static analysis hints.

Apply this diff:

     """Test suite for utility functions."""
 
     # Test case: invalid timestamp types.
-    INVALID_TYPE_ENTRIES = [
+    INVALID_TYPE_ENTRIES: ClassVar[list[dict]] = [
         {
             "timestamp": None,
             "message": '{"message":"Log with None timestamp"}\n',
         },
         {
             "timestamp": "1729267200000",  # str instead of int
             "message": '{"message":"Log with str timestamp"}\n',
         },
         {
             "timestamp": 1729267200000.0,  # float instead of int
             "message": '{"message":"Log with float timestamp"}\n',
         },
     ]
-    INVALID_TYPE_EXPECTED = [
+    INVALID_TYPE_EXPECTED: ClassVar[list[str]] = [
         'timestamp: N/A, message: {"message":"Log with None timestamp"}\n',
         'timestamp: N/A, message: {"message":"Log with str timestamp"}\n',
         'timestamp: N/A, message: {"message":"Log with float timestamp"}\n',
     ]
 
     # Test case: invalid timestamp values.
-    INVALID_VALUE_ENTRIES = [
+    INVALID_VALUE_ENTRIES: ClassVar[list[dict]] = [
         {
             "timestamp": 9999999999999999,
             "message": '{"message":"Log with overflow timestamp"}\n',
         },
         {
             "timestamp": -9999999999999999,
             "message": '{"message":"Log with negative overflow timestamp"}\n',
         },
     ]
-    INVALID_VALUE_EXPECTED = [
+    INVALID_VALUE_EXPECTED: ClassVar[list[str]] = [
         'timestamp: N/A, message: {"message":"Log with overflow timestamp"}\n',
         'timestamp: N/A, message: {"message":"Log with negative overflow timestamp"}\n',
     ]
 
     # Test case: missing timestamp and message fields.
-    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY = [
+    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY: ClassVar[list[dict]] = [
         {
             "_id": "test001",
         }
     ]
-    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED = [
+    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED: ClassVar[list[str]] = [
         "timestamp: N/A, message: "
     ]
 
     # Testing basic functionality.
-    RAW_LOG_ENTRIES = [
+    RAW_LOG_ENTRIES: ClassVar[list[dict]] = [
         {
             "_id": "test000",
             "timestamp": None,
             ...
         },
     ]
 
-    EXPECTED_RESULTS = [
+    EXPECTED_RESULTS: ClassVar[list[str]] = [
         (
             'timestamp: 2024-10-18T16:00:00.123Z, message: '
             ...
         ),
     ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13c1c81 and f38ca67.

📒 Files selected for processing (2)
  • components/clp-mcp-server/clp_mcp_server/server/utils.py (1 hunks)
  • components/clp-mcp-server/tests/server/test_utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
components/clp-mcp-server/tests/server/test_utils.py (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • filter_query_results (33-51)
  • sort_query_results (54-66)
🪛 Ruff (0.14.1)
components/clp-mcp-server/tests/server/test_utils.py

10-23: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


24-28: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


31-40: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


41-44: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


47-51: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


52-54: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


57-96: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


98-119: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: package-image
  • GitHub Check: lint-check (macos-15)
  • GitHub Check: rust-checks (ubuntu-22.04)
  • GitHub Check: rust-checks (macos-15)
  • GitHub Check: rust-checks (ubuntu-24.04)
🔇 Additional comments (2)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)

9-30: LGTM: Robust timestamp conversion implementation.

The function correctly enforces type safety, handles millisecond precision, and provides comprehensive error handling for edge cases.


54-66: LGTM: Robust sorting implementation handles mixed types.

The _key function correctly handles non-integer timestamp types by returning a sentinel value, preventing TypeError during sorting and ensuring invalid timestamps sort last. This aligns well with filter_query_results treating non-int timestamps as "N/A".

Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/server.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py (1)

42-85: Fix control flow: return early on validation failures.

When host or port validation fails (lines 47, 61, 66), exit_code is set to 1 but execution continues to load configuration and start the server. This wastes resources and may cause confusing error messages. Return early when validation fails.

Apply this diff:

     exit_code = 0
 
     # Validate host and port
     if len(host.strip()) == 0:
         logger.error("Host cannot be empty.")
-        exit_code = 1
+        return 1
 
     # Validate host format (IP address or resolvable hostname)
     try:
         ipaddress.ip_address(host)
     except ValueError:
         # If not an IP, try to resolve as hostname
         try:
             socket.gethostbyname(host)
         except OSError:
             logger.exception(
                 "Host validation failed: '%s' is not a valid IP address and DNS resolution failed.",
                 host,
             )
-            exit_code = 1
+            return 1
 
     max_port = 65535
     if port <= 0 or port > max_port:
         logger.error("Port must be between 1 and %d, got: %d.", max_port, port)
-        exit_code = 1
+        return 1
 
     try:
         clp_config = CLPConfig.model_validate(read_yaml_config_file(config_path))
     except ValidationError:
         logger.exception("Configuration validation failed for: %s", config_path)
-        exit_code = 1
+        return 1
     except Exception:
         logger.exception("Failed to load configuration from: %s", config_path)
-        exit_code = 1
+        return 1
 
     try:
         mcp = create_mcp_server(clp_config)
         logger.info("Starting CLP MCP Server on %s:%d.", host, port)
         mcp.run(transport="streamable-http", host=host, port=port)
     except Exception:
         logger.exception("Failed to start MCP server.")
-        exit_code = 1
-
-    return exit_code
+        return 1
+    
+    return 0
♻️ Duplicate comments (4)
components/clp-mcp-server/clp_mcp_server/server/server.py (3)

15-23: Rename parameter to match its type and update docstring.

The parameter is named config_path but its type is CLPConfig, not a path. This is misleading. Rename to clp_config and document it properly.

Apply this diff:

-def create_mcp_server(config_path: CLPConfig) -> FastMCP:
+def create_mcp_server(clp_config: CLPConfig) -> FastMCP:
     """
     Creates and defines API tool calls for the CLP MCP server.
 
-    :param config_path:
+    :param clp_config: The CLP configuration object.
     :return: A configured `FastMCP` instance.
     :raise: Propagates `FastMCP.__init__`'s exceptions.
     :raise: Propagates `FastMCP.tool`'s exceptions.
     """
     mcp = FastMCP(name=constants.SERVER_NAME)
 
     session_manager = SessionManager(session_ttl_seconds=constants.SESSION_TTL_SECONDS)
 
-    connector = ClpConnector(config_path)
+    connector = ClpConnector(clp_config)

Based on learnings.


79-96: Remove magic number and fix duplicate :return: sections in docstring.

The docstring has two issues:

  1. Lines 82-83 hardcode "10 log messages", but this is a configurable constant (NUM_ITEMS_PER_PAGE) that the return value already includes as num_items_per_page.
  2. Lines 87 and 94 both use :return:, creating duplicate return documentation.

Apply this diff:

     """
     Searches the logs for the specified Kibana Query Language (KQL) query and returns the first
-    page of the matching result. The paginated results are ordered from latest to oldest, with
-    each page containing the paging metadata and 10 log messages along with their date string
-    timestamps.
+    page of the matching result. The paginated results are ordered from latest to oldest.
 
     :param kql_query:
     :param ctx: The `FastMCP` context containing the metadata of the underlying MCP session.
-    :return: A dictionary containing the following key-value pairs on success:
+    :return: On success, a dictionary containing the following key-value pairs:
         - "items": A list of log entries in the requested page.
         - "num_total_pages": Total number of pages available from the query as an integer.
         - "num_total_items": Total number of log entries available from the query as an integer.
         - "num_items_per_page": Number of log entries per page.
         - "has_next": Whether a page exists after the returned one.
         - "has_previous": Whether a page exists before the returned one.
-    :return: A dictionary with the following key-value pair on failures:
+        On failure, a dictionary with the following key-value pair:
         - "Error": An error message describing the failure.
     """

Based on learnings.


99-104: Add empty query validation and broaden exception handling.

Two issues:

  1. No validation for empty or whitespace-only kql_query. Submitting an empty query may cause unexpected behavior.
  2. Exception handling only catches ValueError, RuntimeError, and TimeoutError. Database/driver exceptions (e.g., aiomysql.Error, pymongo.errors.PyMongoError) may escape and break the stable {"Error": "..."} response contract.

Apply this diff:

         await session_manager.start()
 
         try:
+            if not kql_query or not kql_query.strip():
+                return {"Error": "Empty KQL query."}
             query_id = await connector.submit_query(kql_query)
             await connector.wait_query_completion(query_id)
             results = await connector.read_results(query_id)
         except (ValueError, RuntimeError, TimeoutError) as e:
             return {"Error": str(e)}
+        except Exception as e:
+            return {"Error": str(e)}
 
         sorted_results = sort_by_timestamp(results)
         filtered_results = filter_query_results(sorted_results)
components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py (1)

68-75: Include config path in error messages for better debugging.

The error messages on lines 71 and 74 don't include the configuration file path, making it harder to debug issues. Include the config_path in the log messages to provide better context.

Apply this diff:

     try:
         clp_config = CLPConfig.model_validate(read_yaml_config_file(config_path))
     except ValidationError:
-        logger.exception("Configuration validation failed.")
+        logger.exception("Configuration validation failed for: %s", config_path)
         exit_code = 1
     except Exception:
-        logger.exception("Failed to load configuration.")
+        logger.exception("Failed to load configuration from: %s", config_path)
         exit_code = 1
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a503e3 and 3a4e859.

📒 Files selected for processing (3)
  • components/clp-mcp-server/clp_mcp_server/clp_connector.py (4 hunks)
  • components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py (3 hunks)
  • components/clp-mcp-server/clp_mcp_server/server/server.py (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-20T20:19:40.504Z
Learnt from: 20001020ycx
PR: y-scope/clp#1436
File: components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py:22-27
Timestamp: 2025-10-20T20:19:40.504Z
Learning: In the clp-mcp-server project (components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py), the --config Click option should use exists=True to validate the configuration file path at option processing time, even though this requires the default path to exist on all machines.

Applied to files:

  • components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py
🧬 Code graph analysis (3)
components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py (3)
components/clp-py-utils/clp_py_utils/clp_config.py (1)
  • CLPConfig (557-739)
components/clp-py-utils/clp_py_utils/core.py (1)
  • read_yaml_config_file (56-62)
components/clp-mcp-server/clp_mcp_server/server/server.py (1)
  • create_mcp_server (15-112)
components/clp-mcp-server/clp_mcp_server/server/server.py (4)
components/clp-py-utils/clp_py_utils/clp_config.py (1)
  • CLPConfig (557-739)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (4)
  • ClpConnector (22-176)
  • submit_query (42-98)
  • wait_query_completion (120-154)
  • read_results (156-176)
components/clp-mcp-server/clp_mcp_server/server/session_manager.py (4)
  • SessionManager (157-237)
  • start (176-179)
  • cache_query_result_and_get_first_page (79-96)
  • cache_query_result_and_get_first_page (194-204)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • filter_query_results (27-47)
  • sort_by_timestamp (50-62)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (1)
components/clp-py-utils/clp_py_utils/clp_config.py (1)
  • CLPConfig (557-739)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: package-image

Comment thread components/clp-mcp-server/clp_mcp_server/clp_connector.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (2)

110-119: Blocker: read_job_status returns raw status (int/str), breaking enum comparisons.

wait_query_completion expects a QueryJobStatus but receives a primitive; leads to “unknown status” and incorrect error paths.

Apply:

-        async with aiomysql.connect(**self._db_conf) as conn, conn.cursor() as cur:
+        async with aiomysql.connect(**self._db_conf) as conn, conn.cursor() as cur:
             await cur.execute("SELECT status FROM query_jobs WHERE id = %s;", (query_id,))
             result = await cur.fetchone()
-            status = result[0] if result else None
+            raw_status = result[0] if result else None
+
+        status = QueryJobStatus(raw_status) if raw_status is not None else None

157-163: Complete return type annotation for read_results.

Use concrete dict typing for clarity.

Apply:

-    async def read_results(self, query_id: str) -> list[dict]:
+    async def read_results(self, query_id: str) -> list[dict[str, Any]]:
♻️ Duplicate comments (5)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (1)

3-5: Type the constructor with CLPConfig and document the parameter.

Improves type safety and clarity.

Apply:

-from typing import Any
+from typing import Any
+from clp_py_utils.clp_config import CLPConfig
@@
-    def __init__(self, clp_config: Any) -> None:
-        """Initializes the ClpConnector with MongoDB and MariaDB configurations."""
+    def __init__(self, clp_config: CLPConfig) -> None:
+        """
+        Initializes the ClpConnector with MongoDB and MariaDB configurations.
+
+        :param clp_config: CLP configuration containing database, results cache and webui settings.
+        """

Also applies to: 26-31

components/clp-mcp-server/tests/server/test_utils.py (1)

121-131: Add dedicated test for mixed-type sorting.

Cover sort_by_timestamp with int/str/float/None timestamps to ensure no TypeError and stable ordering.

Example:

@@
     def test_invalid_timestamp_value(self):
         """Validates the handling of invalid timestamp values."""
         result = format_query_results(self.INVALID_VALUE_ENTRIES)
 
         assert result == self.INVALID_VALUE_EXPECTED
+
+    def test_sort_by_timestamp_mixed_types(self):
+        """sort_by_timestamp handles heterogeneous timestamp types."""
+        mixed = [
+            {"timestamp": 1000, "message": "int 1000"},
+            {"timestamp": "2000", "message": "str 2000"},
+            {"timestamp": 500.5, "message": "float 500.5"},
+            {"timestamp": None, "message": "none"},
+            {"timestamp": 2000, "message": "int 2000"},
+        ]
+        result = sort_by_timestamp(mixed)
+        assert result[0]["timestamp"] == 2000
+        assert result[1]["timestamp"] == 1000
+        assert not isinstance(result[-1]["timestamp"], int)
components/clp-mcp-server/clp_mcp_server/server/server.py (1)

77-94: Harden tool boundary: empty KQL guard and catch‑all to preserve {"Error": "..."} contract.

Prevents submitting blank jobs and leaks of driver exceptions.

Apply:

 @mcp.tool
 async def search_by_kql(kql_query: str, ctx: Context) -> dict[str, Any]:
@@
-        await session_manager.start()
+        await session_manager.start()
@@
-        try:
+        try:
+            if not kql_query or not kql_query.strip():
+                return {"Error": "Empty KQL query."}
             query_id = await connector.submit_query(kql_query)
             await connector.wait_query_completion(query_id)
             results = await connector.read_results(query_id)
         except (ValueError, RuntimeError, TimeoutError) as e:
             return {"Error": str(e)}
+        except Exception as e:
+            return {"Error": str(e)}

Also applies to: 95-109

components/clp-mcp-server/clp_mcp_server/server/utils.py (2)

12-17: Docstring style: use “:raises” with the exception type.

Keep Sphinx conventions consistent.

Apply:

-    :raise: ValueError if `epoch_ts` cannot be converted to a valid date string.
+    :raises ValueError: If `epoch_ts` cannot be converted to a valid date string.

44-46: Coerce message to string to avoid non‑str leakage.

Prevents accidental types (dict/int) from leaking into output.

Apply:

-        message = obj.get("message", "")
-        filtered.append(f"timestamp: {timestamp_str}, message: {message}")
+        message = obj.get("message", "")
+        filtered.append(f"timestamp: {timestamp_str}, message: {str(message)}")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a4e859 and 36bcce2.

📒 Files selected for processing (4)
  • components/clp-mcp-server/clp_mcp_server/clp_connector.py (2 hunks)
  • components/clp-mcp-server/clp_mcp_server/server/server.py (3 hunks)
  • components/clp-mcp-server/clp_mcp_server/server/utils.py (1 hunks)
  • components/clp-mcp-server/tests/server/test_utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
components/clp-mcp-server/clp_mcp_server/server/server.py (4)
components/clp-py-utils/clp_py_utils/clp_config.py (1)
  • CLPConfig (557-739)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (4)
  • ClpConnector (23-177)
  • submit_query (43-99)
  • wait_query_completion (121-155)
  • read_results (157-177)
components/clp-mcp-server/clp_mcp_server/server/session_manager.py (4)
  • SessionManager (157-237)
  • start (176-179)
  • cache_query_result_and_get_first_page (79-96)
  • cache_query_result_and_get_first_page (194-204)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • format_query_results (27-47)
  • sort_by_timestamp (50-62)
components/clp-mcp-server/tests/server/test_utils.py (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • format_query_results (27-47)
  • sort_by_timestamp (50-62)
🪛 Ruff (0.14.1)
components/clp-mcp-server/tests/server/test_utils.py

10-23: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


24-28: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


31-40: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


41-44: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


47-51: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


52-54: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


57-96: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


98-119: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: package-image
  • GitHub Check: lint-check (ubuntu-24.04)
  • GitHub Check: lint-check (macos-15)

Comment thread components/clp-mcp-server/clp_mcp_server/clp_connector.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/server.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py Outdated
@@ -0,0 +1,149 @@
"""Unit tests for CLP MCP server's utility functions."""

from clp_mcp_server.server.utils import format_query_results, sort_by_timestamp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix RUF012: annotate class fixtures with ClassVar.

Mutable class attributes must be marked ClassVar to avoid instance attribute defaults and silence RUF012.

Apply:

-from clp_mcp_server.server.utils import format_query_results, sort_by_timestamp
+from typing import ClassVar
+from clp_mcp_server.server.utils import format_query_results, sort_by_timestamp
@@
-    INVALID_TYPE_ENTRIES = [
+    INVALID_TYPE_ENTRIES: ClassVar[list[dict]] = [
@@
-    INVALID_TYPE_EXPECTED = [
+    INVALID_TYPE_EXPECTED: ClassVar[list[str]] = [
@@
-    INVALID_VALUE_ENTRIES = [
+    INVALID_VALUE_ENTRIES: ClassVar[list[dict]] = [
@@
-    INVALID_VALUE_EXPECTED = [
+    INVALID_VALUE_EXPECTED: ClassVar[list[str]] = [
@@
-    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY = [
+    MISSING_TIMESTAMP_AND_MESSAGE_ENTRY: ClassVar[list[dict]] = [
@@
-    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED = [
+    MISSING_TIMESTAMP_AND_MESSAGE_EXPECTED: ClassVar[list[str]] = [
@@
-    RAW_LOG_ENTRIES = [
+    RAW_LOG_ENTRIES: ClassVar[list[dict]] = [
@@
-    EXPECTED_RESULTS = [
+    EXPECTED_RESULTS: ClassVar[list[str]] = [

As per static analysis hints.

Also applies to: 9-28, 31-44, 47-54, 57-96, 98-119

🤖 Prompt for AI Agents
components/clp-mcp-server/tests/server/test_utils.py lines 3 and ranges 9-28,
31-44, 47-54, 57-96, 98-119: the review flags mutable class-level fixtures that
must be annotated with typing.ClassVar to avoid being treated as instance
defaults (RUF012). Import ClassVar from typing at the top of the file and
annotate each class fixture/mutable class attribute with a ClassVar[...] type
(e.g., my_fixture: ClassVar[FixtureType] = ...) instead of plain assignment so
static analysis recognizes them as class variables; apply this change to all
class-level mutable attributes in the listed line ranges.

@LinZhihao-723 LinZhihao-723 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly docstring comments.
In the meanwhile I will go through the tests.
Otherwise lgtm.

Comment thread components/clp-mcp-server/clp_mcp_server/server/server.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py
logger.warning("Failed to convert epoch timestamp=%s to date string: %s.", epoch, e)

message = obj.get("message", "")
filtered.append(f"timestamp: {timestamp_str}, message: {message}")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious: why don't we return structured log events directly, but with other fields stripped and timestamp formatted as date strings?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

date string is for clear understanding of the time semantic because it is more natural lanuage process friendly compared to epoch.

Stripping other fields are my personal choice as I dont think they are useful and would consume extra token use.

Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py Outdated
Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py
Comment thread components/clp-mcp-server/clp_mcp_server/server/server.py Outdated
Co-authored-by: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com>

@LinZhihao-723 LinZhihao-723 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small fixes to unit tests.

Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated
Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated
Comment thread components/clp-mcp-server/tests/server/test_utils.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (8)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (2)

26-27: Document the clp_config parameter in the constructor docstring.

The clp_config parameter is missing from the docstring, as previously noted in review comments.

Apply this diff to add the parameter documentation:

     def __init__(self, clp_config: Any) -> None:
-        """Initializes the ClpConnector with MongoDB and MariaDB configurations."""
+        """
+        Initializes the ClpConnector with MongoDB and MariaDB configurations.
+        
+        :param clp_config: The CLP configuration object containing database and cache settings.
+        """

53-57: Use standard Sphinx :raises format for exception documentation.

The current :raise: format is non-standard. Sphinx convention uses :raises ExceptionType: description.

Apply this diff:

-        :raise: ValueError if `end_ts` is smaller than `begin_ts`.
-        :raise: RuntimeError if it fails to retrieve the ID of the submitted query.
-        :raise: aiomysql.Error if there is an error connecting to or querying MariaDB.
-        :raise: pymongo.errors.PyMongoError if there is an error interacting with MongoDB.
-        :raise: Exception for any other unexpected errors.
+        :raises ValueError: If `end_ts` is smaller than `begin_ts`.
+        :raises RuntimeError: If it fails to retrieve the ID of the submitted query.
+        :raises aiomysql.Error: If there is an error connecting to or querying MariaDB.
+        :raises pymongo.errors.PyMongoError: If there is an error interacting with MongoDB.
+        :raises Exception: For any other unexpected errors.
components/clp-mcp-server/clp_mcp_server/server/server.py (3)

77-94: Consolidate duplicate :return: sections in docstring.

The docstring has two :return: sections (lines 85 and 92), which is non-standard. Consolidate into a single section describing both success and failure cases.

Apply this diff:

         :param kql_query:
         :param ctx: The `FastMCP` context containing the metadata of the underlying MCP session.
-        :return: A dictionary containing the following key-value pairs on success:
+        :return: On success, a dictionary containing the following key-value pairs:
             - "items": A list of log entries in the requested page.
             - "num_total_pages": Total number of pages available from the query as an integer.
             - "num_total_items": Total number of log entries available from the query as an integer.
             - "num_items_per_page": Number of log entries per page.
             - "has_next": Whether a page exists after the returned one.
             - "has_previous": Whether a page exists before the returned one.
-        :return: A dictionary with the following key-value pair on failures:
+            On failure, a dictionary with the following key-value pair:
             - "Error": An error message describing the failure.

97-102: Add validation for empty KQL query.

The function doesn't validate whether kql_query is empty before submitting it, which could result in unnecessary database operations.

Apply this diff to add validation:

         await session_manager.start()
 
         try:
+            if not kql_query or not kql_query.strip():
+                return {"Error": "Empty KQL query."}
             query_id = await connector.submit_query(kql_query)
             await connector.wait_query_completion(query_id)
             results = await connector.read_results(query_id)

101-102: Broaden exception handling to catch database exceptions.

The current exception tuple only catches (ValueError, RuntimeError, TimeoutError), but connector.submit_query(), wait_query_completion(), and read_results() can raise aiomysql.Error and pymongo.errors.PyMongoError per their docstrings. These exceptions will escape the handler and break the stable {"Error": "..."} response shape.

Apply this diff to add a catch-all handler:

         except (ValueError, RuntimeError, TimeoutError) as e:
             return {"Error": str(e)}
+        except Exception as e:
+            return {"Error": str(e)}
components/clp-mcp-server/clp_mcp_server/server/utils.py (3)

39-53: Rename variable to reflect its purpose.

The variable filtered (line 39) is misleading since the function formats results rather than filtering them. A previous review comment suggested formatted_log_events.

Apply this diff:

-    filtered = []
+    formatted_log_events = []
     for obj in query_results:
         epoch = obj.get("timestamp")
         timestamp_str = TIMESTAMP_NOT_AVAILABLE
 
         if isinstance(epoch, int):
             try:
                 timestamp_str = convert_epoch_to_date_string(epoch)
             except (TypeError, ValueError) as e:
                 logger.warning("Failed to convert epoch timestamp=%s to date string: %s.", epoch, e)
 
         message = obj.get("message", "")
-        filtered.append(f"timestamp: {timestamp_str}, message: {message}")
+        formatted_log_events.append(f"timestamp: {timestamp_str}, message: {message}")
 
-    return filtered
+    return formatted_log_events

56-68: Expand docstring to clarify sorting behaviour and timestamp handling.

The docstring should explicitly document the in-place sorting, expected timestamp format, and handling of missing/invalid timestamps, as suggested in previous review comments.

Apply this diff:

     """
+    Sorts the query results in-place by timestamp in descending order (latest to oldest).
+
+    NOTE:
+    - Timestamp is expected to be an integer representing the epoch timestamp in milliseconds,
+      stored under the `timestamp` key.
+    - If `timestamp` is missing or not an integer, it is treated as the oldest possible timestamp.
+
     :param query_results: A list of dictionaries representing kv-pair log events.
-    :return: The same list, sorted in-place by epoch from latest to oldest.
+    :return: The input list sorted in-place.
     """

12-24: Use standard Sphinx :raises format in docstring.

Line 16 uses :raise: but the standard Sphinx format is :raises ValueError:.

Apply this diff:

-    :raise: ValueError if `epoch_ts` cannot be converted to a valid date string.
+    :raises ValueError: If `epoch_ts` cannot be converted to a valid date string.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36bcce2 and 66fb03d.

📒 Files selected for processing (3)
  • components/clp-mcp-server/clp_mcp_server/clp_connector.py (2 hunks)
  • components/clp-mcp-server/clp_mcp_server/server/server.py (3 hunks)
  • components/clp-mcp-server/clp_mcp_server/server/utils.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-20T20:19:40.504Z
Learnt from: 20001020ycx
PR: y-scope/clp#1436
File: components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py:22-27
Timestamp: 2025-10-20T20:19:40.504Z
Learning: In the clp-mcp-server project (components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py), the --config Click option should use exists=True to validate the configuration file path at option processing time, even though this requires the default path to exist on all machines.

Applied to files:

  • components/clp-mcp-server/clp_mcp_server/server/server.py
🧬 Code graph analysis (1)
components/clp-mcp-server/clp_mcp_server/server/server.py (4)
components/clp-py-utils/clp_py_utils/clp_config.py (1)
  • CLPConfig (557-739)
components/clp-mcp-server/clp_mcp_server/clp_connector.py (4)
  • ClpConnector (23-177)
  • submit_query (43-99)
  • wait_query_completion (121-155)
  • read_results (157-177)
components/clp-mcp-server/clp_mcp_server/server/session_manager.py (4)
  • SessionManager (157-237)
  • start (176-179)
  • cache_query_result_and_get_first_page (79-96)
  • cache_query_result_and_get_first_page (194-204)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)
  • format_query_results (27-53)
  • sort_by_timestamp (56-68)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: package-image
  • GitHub Check: lint-check (ubuntu-24.04)
  • GitHub Check: lint-check (macos-15)
🔇 Additional comments (2)
components/clp-mcp-server/clp_mcp_server/server/server.py (2)

5-5: LGTM!

The new imports correctly support the KQL search feature: CLPConfig for configuration, ClpConnector for query execution, and utilities for result processing.

Also applies to: 8-8, 12-12


28-28: LGTM!

The connector is correctly instantiated with the provided configuration.

Comment thread components/clp-mcp-server/clp_mcp_server/clp_connector.py
Comment thread components/clp-mcp-server/clp_mcp_server/server/server.py

@LinZhihao-723 LinZhihao-723 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the PR title, how about:

feat(clp-mcp-server): Add `search_by_kql` MCP tool call.
  • to make it concise
  • to add the missing period

@20001020ycx 20001020ycx changed the title feat(clp-mcp-server): Implement search_by_kql tool call in MCP Server feat(clp-mcp-server): feaet(clp-mcp-server): Add search_by_kql MCP tool call. Oct 22, 2025
@20001020ycx 20001020ycx changed the title feat(clp-mcp-server): feaet(clp-mcp-server): Add search_by_kql MCP tool call. feaet(clp-mcp-server): Add search_by_kql MCP tool call. Oct 22, 2025
@20001020ycx 20001020ycx changed the title feaet(clp-mcp-server): Add search_by_kql MCP tool call. feat(clp-mcp-server): Add search_by_kql MCP tool call. Oct 22, 2025
@LinZhihao-723 LinZhihao-723 merged commit b059fa0 into y-scope:main Oct 22, 2025
22 checks passed
junhaoliao pushed a commit to junhaoliao/clp that referenced this pull request May 17, 2026
Co-authored-by: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants