Skip to content

fix(clp-mcp-server): Include log viewer links in formatted log events returned from KQL queries.#1491

Merged
20001020ycx merged 19 commits into
y-scope:mainfrom
20001020ycx:10-24-link-regression
Oct 26, 2025
Merged

fix(clp-mcp-server): Include log viewer links in formatted log events returned from KQL queries.#1491
20001020ycx merged 19 commits into
y-scope:mainfrom
20001020ycx:10-24-link-regression

Conversation

@20001020ycx

@20001020ycx 20001020ycx commented Oct 24, 2025

Copy link
Copy Markdown
Contributor

Description

This PR adds the missing link field in the search result returned by the kql search query. The field is used by LLM to create hyperlinks when displaying the log messages. The current implementation filters this field away when formatting the query result.

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/test_utils.py -vv
  2. Lint Tests: 1uv run ruff check clp/components/clp-mcp-server && task lint:check-py.1
  3. Manual Integration Tests: Built using task docker-images:package. Connected Claude Desktop agent to the running MCP server service in docker compose. Manually instructed LLM to perform a kql that returns a log event, the agent can successfully render the log viewer's link:
Screenshot 2025-10-24 at 1 46 08 PM

And this is the interface when hitting the link - "View full log entry", which clearly shows the returned log event:

Screenshot 2025-10-24 at 1 48 04 PM

Summary by CodeRabbit

  • New Features

    • Formatted log lines now include a link suffix when a link value is present.
  • Bug Fixes

    • Formatting now requires a link field; absent links can raise errors instead of being ignored.
  • Tests

    • Test fixtures and expected outputs updated to cover link-present and link-missing scenarios.
  • Documentation

    • Docstring updated to document the link field and its formatted output.

@coderabbitai

coderabbitai Bot commented Oct 24, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

format_query_results now requires each log event to include a link key; the function reads obj["link"] and appends , link: <link> to each formatted line. Tests and fixtures were updated to include a LINK constant and corresponding link values/expected outputs.

Changes

Cohort / File(s) Summary
Formatter change
components/clp-mcp-server/clp_mcp_server/server/utils.py
format_query_results(query_results: list[dict[str, Any]]) -> list[str] updated to read obj["link"] and append , link: <link> to each formatted string. Docstring updated to document link. Implementation accesses link with direct indexing (may raise KeyError if absent).
Tests updated
components/clp-mcp-server/tests/server/test_utils.py
Added LINK constant in TestUtils; updated RAW_LOG_ENTRIES and EXPECTED_RESULTS to include link fields and expect the appended , link: <URL> in formatted outputs.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Formatter as format_query_results
    Caller->>Formatter: format_query_results(query_results)
    loop per event
        Formatter->>Formatter: parse timestamp, message, other kv-pairs
        Formatter->>Formatter: read obj["link"] (required)
        alt link present
            Formatter-->>Caller: formatted string with ", link: <link>"
        else link missing
            Formatter-->>Caller: raises KeyError (no fallback)
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Verify unconditional obj["link"] access in utils.py and decide whether to add fallback/validation or document requirement.
  • Confirm exact formatting (commas, spacing) matches updated test expectations in test_utils.py.

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.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title Check ✅ Passed The pull request title "fix(clp-mcp-server): Include log viewer links in formatted log events returned from KQL queries" directly and fully summarizes the main change in the changeset. The title is specific and clearly conveys that the PR adds "link" fields to formatted log events from KQL query results, which aligns precisely with the modifications shown in the raw summary where format_query_results now appends link information to each formatted log line. The title is concise, avoids vague language or noise, and provides sufficient clarity that a teammate scanning the commit history would immediately understand the primary objective of this change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 10 24 link regression fix(clp-mcp-server): Fix the missing link field in the log search result. Oct 24, 2025
@20001020ycx 20001020ycx changed the title fix(clp-mcp-server): Fix the missing link field in the log search result. fix(clp-mcp-server): Add the missing link field in the KQL search result. Oct 24, 2025
@20001020ycx 20001020ycx changed the title fix(clp-mcp-server): Add the missing link field in the KQL search result. fix(clp-mcp-server): Add the missing link field in the returned result from KQL search. Oct 24, 2025
@20001020ycx 20001020ycx changed the title fix(clp-mcp-server): Add the missing link field in the returned result from KQL search. fix(clp-mcp-server): Add link field in the returned result from KQL search. Oct 24, 2025
@20001020ycx 20001020ycx marked this pull request as ready for review October 24, 2025 17:49
@20001020ycx 20001020ycx requested a review from a team as a code owner October 24, 2025 17:49

@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

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/tests/server/test_utils.py (1)

210-215: Consider adding test coverage for warning behaviour.

While the functional output is tested correctly, there's no explicit verification that warnings are logged when the link field is missing. Consider adding a test with caplog fixture to verify the warning behaviour, especially since 3 out of 4 entries in the test data lack links.

Example:

def test_missing_link_warning(self, caplog):
    """Validates that missing link fields generate warnings."""
    entries_without_links = [entry for entry in self.RAW_LOG_ENTRIES if "link" not in entry]
    
    with caplog.at_level(logging.WARNING):
        format_query_results(entries_without_links)
    
    # Verify warnings were logged for each entry without a link
    assert len([record for record in caplog.records if "Empty link" in record.message]) == len(entries_without_links)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb7f0d5 and c6d0f6d.

📒 Files selected for processing (2)
  • components/clp-mcp-server/clp_mcp_server/server/utils.py (2 hunks)
  • components/clp-mcp-server/tests/server/test_utils.py (1 hunks)
⏰ 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
🔇 Additional comments (2)
components/clp-mcp-server/tests/server/test_utils.py (2)

116-119: LGTM!

The test data correctly includes a link field with a properly formatted URL. The link parameters (streamId and logEventIdx) appropriately match the corresponding archive_id and log_event_ix fields in the same entry.


127-129: Expected result updated correctly.

The expected output format matches the implementation and correctly includes the link field.

Comment on lines +86 to +89
- "link": (Optional) A string representing the link to open the log viewer displaying the
message.

The message will be formatted as `timestamp: <date string>, message: <message>`:
The message will be formatted as `timestamp: <date string>, message: <message>, link: <link>`.

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.

🧹 Nitpick | 🔵 Trivial

Consider documenting the warning behaviour for missing links.

The docstring indicates that the link field is optional, but doesn't mention that a warning will be logged when it's missing (see lines 111-112). For completeness, consider adding a note about this behaviour, especially since it might be unexpected for callers who view the field as truly optional.

🤖 Prompt for AI Agents
In components/clp-mcp-server/clp_mcp_server/server/utils.py around lines 86 to
89, the docstring describes "link" as optional but omits that a warning is
emitted when it is missing (see code at ~lines 111-112); update the docstring to
state that while the link field may be None, the function will log a warning if
the link is not provided so callers are aware of the runtime behavior and can
avoid or handle the warning.

Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py Outdated
Comment on lines +117 to +118
'http://localhost:4000/streamFile?dataset=default&'
'type=json&streamId=abc125&logEventIdx=102'

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.

Suggested change
'http://localhost:4000/streamFile?dataset=default&'
'type=json&streamId=abc125&logEventIdx=102'
"http://localhost:4000/streamFile?dataset=default&"
"type=json&streamId=abc125&logEventIdx=102"

Technically the line wrapping here is also wrong...

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.

the line wrapping here is also wrong

do you mean?

Suggested change
'http://localhost:4000/streamFile?dataset=default&'
'type=json&streamId=abc125&logEventIdx=102'
"http://localhost:4000/streamFile"
"?dataset=default"
'&type=json"
"&streamId=abc125"
"&logEventIdx=102"

i agree

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.

I thought single quotation and double quotation were the same, its just that single quotation is used for escaption the actual double quotation in the string?

I see, I will make a note on the line wrap

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

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

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 a sanity check: shouldn't this link always exist, assuming the rusult is from clp-s? @junhaoliao Can u confirm?

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.

In fact, the webui link is derived using the following logic in clp_connector:

async for doc in collection.find({}, limit=SEARCH_MAX_NUM_RESULTS):
  doc["link"] = (
      f"{self._webui_addr}/streamFile?type=json"
      f'&streamId={doc["archive_id"]}'
      f"&dataset={CLP_DEFAULT_DATASET_NAME}"
      f'&logEventIdx={doc["log_event_ix"]}'
  )
  doc["_id"] = None
  results.append(doc)

where collection is the result read from MongoDB and results is the response we will return back to AI after formatting.

To answer the question, this link should always exist in the current implementation. But shouldn't we still preserve this if checking (whether the link is there or not) in case we change the implementation of derving the link in the future?

20001020ycx and others added 2 commits October 25, 2025 04:58
Co-authored-by: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com>
Co-authored-by: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com>

@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 (1)
components/clp-mcp-server/clp_mcp_server/server/utils.py (1)

110-110: Consider adding type validation for the link field.

The code doesn't validate that link is actually a string. If the field is present but has an unexpected type (e.g., None, int, list), it will be coerced to a string in the f-string on line 116, potentially producing unexpected output like "link: None" or "link: [1, 2, 3]".

Add a type check after extracting the link:

 link = obj.get("link", "")
+if link and not isinstance(link, str):
+    logger.warning("Non-string link field in log event: %s.", obj)
+    link = ""
 if not link:
     logger.warning("No valid link provided in log event: %s.", obj)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c2e477 and 8c22bb9.

📒 Files selected for processing (1)
  • components/clp-mcp-server/clp_mcp_server/server/utils.py (2 hunks)
⏰ 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
🔇 Additional comments (2)
components/clp-mcp-server/clp_mcp_server/server/utils.py (2)

86-87: LGTM!

The docstring clearly documents the optional "link" field and its purpose.


89-89: LGTM!

The formatted output example clearly shows the optional link field using appropriate notation.

Comment on lines +110 to +116
link = obj.get("link", "")
if not link:
logger.warning("No valid link provided in log event: %s.", obj)
formatted_log_events.append(f"timestamp: {timestamp_str}, message: {message}")
continue

formatted_log_events.append(f"timestamp: {timestamp_str}, message: {message}, link: {link}")

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.

🧹 Nitpick | 🔵 Trivial

Eliminate string duplication by conditionally building the formatted entry.

The formatting string is duplicated on lines 113 and 116, differing only in the link portion. This violates DRY and makes the code harder to maintain—future format changes must be applied in two places.

Refactor to build the formatted string conditionally:

 link = obj.get("link", "")
 if not link:
     logger.warning("No valid link provided in log event: %s.", obj)
-    formatted_log_events.append(f"timestamp: {timestamp_str}, message: {message}")
-    continue
 
-formatted_log_events.append(f"timestamp: {timestamp_str}, message: {message}, link: {link}")
+formatted_entry = f"timestamp: {timestamp_str}, message: {message}"
+if link:
+    formatted_entry += f", link: {link}"
+formatted_log_events.append(formatted_entry)
🤖 Prompt for AI Agents
In components/clp-mcp-server/clp_mcp_server/server/utils.py around lines
110-116, the formatted log string is duplicated; instead, build a single base
formatted string (timestamp and message), if link is present append the ", link:
{link}" fragment, then append the result to formatted_log_events; keep the
existing logger.warning when link is missing but remove the duplicated
formatted_log_events.append call so the final append happens once.

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c22bb9 and ec97b64.

📒 Files selected for processing (1)
  • components/clp-mcp-server/tests/server/test_utils.py (1 hunks)
⏰ 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 on lines +116 to +122
"link": (
"http://localhost:4000/streamFile"
"?dataset=default"
'&type=json'
"&streamId=abc125"
"&logEventIdx=102"
),

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.

🧹 Nitpick | 🔵 Trivial

Use consistent quote style.

Line 119 uses single quotes ('&type=json') while the surrounding lines use double quotes. For consistency and adherence to Python style conventions, use double quotes throughout the string concatenation.

Apply this diff to fix the quote consistency:

             "link": (
                 "http://localhost:4000/streamFile"
                 "?dataset=default"
-                '&type=json'
+                "&type=json"
                 "&streamId=abc125"
                 "&logEventIdx=102"
             ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"link": (
"http://localhost:4000/streamFile"
"?dataset=default"
'&type=json'
"&streamId=abc125"
"&logEventIdx=102"
),
"link": (
"http://localhost:4000/streamFile"
"?dataset=default"
"&type=json"
"&streamId=abc125"
"&logEventIdx=102"
),
🤖 Prompt for AI Agents
components/clp-mcp-server/tests/server/test_utils.py around lines 116 to 122:
the URL string concatenation uses a single-quoted segment ('&type=json') while
all other parts use double quotes; replace the single-quoted '&type=json' with a
double-quoted "&type=json" so the entire concatenation uses consistent double
quotes.

Comment on lines +130 to +136
'"message":"Log with millisecond precision"}\n, '
"link: http://localhost:4000/streamFile"
"?dataset=default"
'&type=json'
"&streamId=abc125"
"&logEventIdx=102"

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.

🧹 Nitpick | 🔵 Trivial

Use consistent quote style and remove trailing whitespace.

Line 133 uses single quotes ('&type=json') while the surrounding lines use double quotes. Additionally, line 136 appears to contain trailing whitespace.

Apply this diff to fix both issues:

             'timestamp: 2024-10-18T16:00:00.123Z, message: '
             '{"ts":1729267200123,"pid":1234,"tid":5678,'
             '"message":"Log with millisecond precision"}\n, '
             "link: http://localhost:4000/streamFile"
             "?dataset=default"
-            '&type=json'
+            "&type=json"
             "&streamId=abc125"
             "&logEventIdx=102"
-            
         ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'"message":"Log with millisecond precision"}\n, '
"link: http://localhost:4000/streamFile"
"?dataset=default"
'&type=json'
"&streamId=abc125"
"&logEventIdx=102"
'"message":"Log with millisecond precision"}\n, '
"link: http://localhost:4000/streamFile"
"?dataset=default"
"&type=json"
"&streamId=abc125"
"&logEventIdx=102"
),
🤖 Prompt for AI Agents
In components/clp-mcp-server/tests/server/test_utils.py around lines 130 to 136,
change the inconsistent single-quoted string on line 133 to use double quotes to
match the surrounding lines and remove any trailing whitespace on line 136 (and
at line ends in the block) so all concatenated string pieces consistently use
double quotes and no lines end with extra spaces.

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

@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.

Let me know when the e2e testing is done.

Comment thread components/clp-mcp-server/clp_mcp_server/server/utils.py Outdated
Co-authored-by: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com>
@LinZhihao-723 LinZhihao-723 changed the title fix(clp-mcp-server): Add link field in the returned result from KQL search. fix(clp-mcp-server): Include log viewer links in formatted log events returned from KQL queries. Oct 25, 2025

@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.

Directly modified PR title. Let me know if it's ok.

@20001020ycx 20001020ycx merged commit 1677431 into y-scope:main Oct 26, 2025
26 of 37 checks passed
junhaoliao pushed a commit to junhaoliao/clp that referenced this pull request May 17, 2026
… returned from KQL queries. (y-scope#1491)

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