Skip to content

feat: support passing token to mcp in header - #1397

Merged
arikalon1 merged 3 commits into
HolmesGPT:masterfrom
gossion:guwe/0121-ctx-header
Jan 26, 2026
Merged

arikalon1 merged 3 commits into
HolmesGPT:masterfrom
gossion:guwe/0121-ctx-header

Conversation

@gossion

@gossion gossion commented Jan 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Per-request context is now propagated across investigations, chats, streaming flows, and tool invocations.
    • Remote toolsets support templated extra-headers with env and request-context substitutions (case-insensitive).
  • Bug Fixes

    • API endpoints map auth and rate-limit failures to consistent HTTP status codes.
    • Sensitive values in request context are redacted in dumps/logs.
  • Documentation

    • Added docs for dynamic headers with request-context and examples.
  • Tests

    • Expanded tests for header rendering, context passthrough, and header extraction.

✏️ Tip: You can customize this high-level summary in your review settings.

@netlify

netlify Bot commented Jan 21, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for holmes-docs ready!

Name Link
🔨 Latest commit d38842c
🔍 Latest deploy log https://app.netlify.com/projects/holmes-docs/deploys/697769f8f290a50008cf8d32
😎 Deploy Preview https://deploy-preview-1397--holmes-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jan 21, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

Threads an optional request_context (derived from incoming HTTP headers) through server endpoints, investigation entry points, the LLM/tool-calling pipeline, ToolInvokeContext, and the Remote MCP toolset header rendering and session initialization.

Changes

Cohort / File(s) Summary
Investigation Core Context Threading
holmes/core/investigation.py
Added request_context: Optional[Dict[str, Any]] to investigate_issues() signature and forwarded it into AI investigation calls; expanded typing imports.
LLM Tool Calling Context Threading
holmes/core/tool_calling_llm.py
Added request_context parameter across public/internal methods (process_tool_decisions, prompt_call, messages_call, call, call_stream, _directly_invoke_tool_call, _get_tool_call_result, _invoke_llm_tool_call, _handle_tool_call_approval, investigate) and propagated it through tool invocation and streaming flows.
Tool Invocation Context Management
holmes/core/tools.py
Added request_context: Optional[Dict[str, Any]] field to ToolInvokeContext; implemented model_dump() to redact values and __str__() to expose only context keys for safe logging.
MCP Toolset Header Rendering
holmes/plugins/toolsets/mcp/toolset_mcp.py
Added CaseInsensitiveDict, extra_headers on MCPConfig, _render_headers() and _render_template() to render {{ request_context.headers[...] }} (case-insensitive) and {{ env.VAR }}; get_initialized_mcp_session() and RemoteMCPTool._invoke_async() now accept/propagate request_context and use rendered headers.
Server Endpoint Header Extraction & Context Propagation
server.py
Added extract_passthrough_headers(request) to collect non-blocked headers into request_context; endpoints updated to accept Request and pass request_context downstream; mapped AuthenticationError→401 and RateLimitError→429.
Tests — MCP & Server & Approval
tests/test_mcp_toolset.py, tests/test_server_endpoints.py, tests/test_approval_workflow.py
Updated tests to exercise request_context threading and RemoteMCPTool._invoke_async(..., request_context); added header/template rendering tests, env substitution and redaction checks, extract_passthrough_headers unit tests, and adjusted mocks to accept request_context.
Docs
docs/data-sources/remote-mcp-servers.md
Documented dynamic extra_headers with templating using request_context.headers and env.VAR, added examples and usage notes.

Sequence Diagram(s)

sequenceDiagram
    participant Client as HTTP Client
    participant Server as Server (server.py)
    participant Investigation as Investigation (holmes/core/investigation.py)
    participant LLMCaller as LLM/Tool Caller (holmes/core/tool_calling_llm.py)
    participant ToolInvoke as ToolInvokeContext (holmes/core/tools.py)
    participant MCPToolset as Remote MCP Toolset (holmes/plugins/toolsets/mcp/toolset_mcp.py)

    Client->>Server: POST /investigate (with headers)
    Server->>Server: extract_passthrough_headers(request) -> request_context
    Server->>Investigation: investigate_issues(..., request_context)
    Investigation->>LLMCaller: prompt_call(..., request_context)
    LLMCaller->>LLMCaller: process_tool_decisions(..., request_context)
    LLMCaller->>ToolInvoke: _directly_invoke_tool_call(..., request_context)
    ToolInvoke->>MCPToolset: get_initialized_mcp_session(request_context)
    MCPToolset->>MCPToolset: _render_headers(request_context) // render {{ request_context }} & {{ env.VAR }}
    MCPToolset->>MCPToolset: _invoke_async(..., request_context) with rendered headers
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • moshemorad
  • arikalon1
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: support passing token to mcp in header' directly describes the main functionality added in this changeset - enabling request context and header propagation to MCP backends.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

@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 (2)
tests/test_approval_workflow.py (1)

135-142: Silence Ruff ARG001/ARG005 for unused mock args.
These new params are required for signature compatibility but unused in tests.

🧹 Suggested lint-safe tweak
-    def mock_invoke_tool(
+    def mock_invoke_tool(
         tool_name: str,
         tool_params: dict,
         user_approved: bool,
         tool_call_id: str,
         tool_number: Optional[int] = None,
-        request_context: Optional[dict] = None,
-    ) -> StructuredToolResult:
+        request_context: Optional[dict] = None,
+    ) -> StructuredToolResult:  # noqa: ARG001
@@
-    ai.process_tool_decisions = MagicMock(
-        side_effect=lambda messages, tool_decisions, request_context=None: (
+    ai.process_tool_decisions = MagicMock(
+        side_effect=lambda messages, tool_decisions, request_context=None: (  # noqa: ARG005
             messages
@@
-    ai.process_tool_decisions = MagicMock(
-        side_effect=lambda messages, tool_decisions, request_context=None: (
+    ai.process_tool_decisions = MagicMock(
+        side_effect=lambda messages, tool_decisions, request_context=None: (  # noqa: ARG005
             messages

Also applies to: 268-270, 415-417

server.py (1)

254-303: Merge request body context field into request context for all non-investigate endpoints.

All four request classes (WorkloadHealthRequest, WorkloadHealthChatRequest, IssueChatRequest, ChatRequest) define a context field, but the endpoints extract only passthrough headers and never merge the request body's context. This silently drops client-provided context data.

The issue exists in all four endpoints:

  • /api/workload_health_check (line 297)
  • /api/workload_health_chat (line 338)
  • /api/issue_chat (line 370)
  • /api/chat (line 432)
✅ Suggested fix (apply to each endpoint)
-        request_context = extract_passthrough_headers(http_request)
+        request_context = extract_passthrough_headers(http_request)
+        if request.context:
+            request_context.update(request.context)

Replace request with the actual parameter name for each endpoint (issue_chat_request, chat_request, etc.).

🤖 Fix all issues with AI agents
In `@design/aks-multi-cluster-cross-cluster-investigation.md`:
- Around line 117-123: Replace the JWT-looking example values in the HTTP
request sample (the Authorization and X-Azure-Token header values) with explicit
placeholders like <HOLMES_SERVER_TOKEN> and <AZURE_ACCESS_TOKEN> (and similarly
update the other occurrences noted at lines 191-197 and 242-246); edit the
request block that contains "Authorization: Bearer <holmes-server-token>" and
"X-Azure-Token: eyJ0eXAiOiJKV1QiLCJhbG..." to use non-secret placeholders to
avoid secret-scanner triggering.
- Line 19: The markdown headings and fenced code blocks in the document (e.g.,
the heading "Headers Pass-through with Template-based Configuration" and other
similar headings noted) do not follow markdownlint rules; update each heading to
use proper Markdown heading syntax (consistent `#/`## levels) and add explicit
fenced-code languages to all code blocks (examples: ```http, ```json, ```yaml,
```bash, ```python) so that linting and syntax highlighting work correctly;
apply the same fixes to the other occurrences referenced in the review (the
similar headings/code fences elsewhere in the doc).

In `@holmes/core/tools.py`:
- Around line 165-175: The model_dump override currently accesses
data["request_context"] directly; to satisfy RUF019 replace those direct index
accesses with dict.get to avoid KeyError warnings: use request_ctx =
data.get("request_context") and then if request_ctx: set data["request_context"]
= {k: "***REDACTED***" for k in request_ctx.keys()} (keep the same behavior and
return data) — update the model_dump function to use data.get("request_context")
instead of data["request_context"].

In `@holmes/plugins/toolsets/mcp/toolset_mcp.py`:
- Around line 125-131: The request_context is being set on the toolset before
acquiring the per-server lock causing races; move the call to
self.toolset.set_request_context(context.request_context) inside the with lock:
block (near where get_server_lock(...) is used) so the assignment is serialized
with the lock, then wrap the asyncio.run(self._invoke_async(params)) call in a
try/finally and clear the request context in the finally (e.g., call
self.toolset.set_request_context(None) or a provided clear method) to ensure
_render_headers() never reads a stale context after the request completes.
- Around line 231-310: In _render_template, the current re.sub calls use raw
replacement strings which can be misinterpreted (backrefs, escapes); update both
re.sub calls (the one that replaces request_context header patterns and the one
that replaces env variable patterns) to pass a replacement function/lambda that
returns the replacement string (ensuring it's a str) so replacements are treated
literally and backslashes/backreferences aren't processed.

In `@server.py`:
- Around line 398-414: extract_passthrough_headers currently title-cases header
names (e.g., X-Api-Key) but _render_template performs case-sensitive lookups
(e.g., X-API-Key) causing misses; make header handling case-insensitive by
normalizing keys to a single canonical form (recommend lowercase) in
extract_passthrough_headers (produce headers dict with lowercase keys) and
update _render_template to normalize any lookup key to lowercase (or wrap
request_context.headers in a case-insensitive mapping) so templates can use any
case and still resolve values; change references in extract_passthrough_headers
and _render_template accordingly.

In `@tests/test_mcp_toolset.py`:
- Around line 1322-1326: The test currently assigns rendered =
mock_toolset._render_headers() but never uses it (F841); update the assertion to
also validate rendered so the value is used and the test is stronger — for
example, assert that rendered contains the expected result or error indicator
returned by mock_toolset._render_headers() (in addition to checking
caplog.text), referencing the call to mock_toolset._render_headers(), the
rendered variable, and the existing log check for "Header key 'X-Nonexistent'
not found in request_context".
- Around line 142-170: Replace the unused fixture parameter usage in the test
functions by applying pytest.mark.usefixtures("suppress_migration_warnings") to
the test functions instead of accepting suppress_migration_warnings as a
parameter; specifically, update test_toolset_returns_configured_extra_headers
and test_toolset_without_extra_headers_returns_none (and other tests mentioned)
to remove the suppress_migration_warnings argument and add
`@pytest.mark.usefixtures`("suppress_migration_warnings") above each test so Ruff
ARG002 is resolved while preserving the fixture's side effects.

Comment thread design/aks-multi-cluster-cross-cluster-investigation.md Outdated
Comment thread design/aks-multi-cluster-cross-cluster-investigation.md Outdated
Comment thread holmes/core/tools.py
Comment thread holmes/plugins/toolsets/mcp/toolset_mcp.py Outdated
Comment thread holmes/plugins/toolsets/mcp/toolset_mcp.py Outdated
Comment thread server.py
Comment thread tests/test_mcp_toolset.py Outdated
Comment thread tests/test_mcp_toolset.py Outdated
@gossion gossion changed the title feat: support query on multiple cluster via mcp feat: support passing token to mcp in header Jan 21, 2026

@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

🤖 Fix all issues with AI agents
In `@tests/test_mcp_toolset.py`:
- Line 10: Remove the unused import of LLM from the test file: delete the line
importing "LLM" from "holmes.core.llm" so that the test module no longer has an
unused symbol; ensure no other references to LLM exist in
tests/test_mcp_toolset.py and run tests to confirm.
♻️ Duplicate comments (4)
tests/test_mcp_toolset.py (2)

1140-1143: Apply @pytest.mark.usefixtures at class level to fix ARG002 warnings.

All tests in TestHeaderTemplateRendering use suppress_migration_warnings for side effects only. Apply the decorator at class level and remove the parameter from each test method to satisfy Ruff's unused argument check.

Suggested fix
+@pytest.mark.usefixtures("suppress_migration_warnings")
 class TestHeaderTemplateRendering:
     """Test header template rendering functionality"""
 
-    def test_render_hardcoded_headers(self, suppress_migration_warnings):
+    def test_render_hardcoded_headers(self):
         """Test rendering of hardcoded header values"""

Remove the suppress_migration_warnings parameter from all test methods in this class.


1322-1326: Use rendered variable to strengthen the test and fix F841.

The rendered variable is assigned but never used. Assert on its value to strengthen the test and resolve the Ruff warning.

Suggested fix
         mock_toolset.set_request_context({"headers": {"X-Other": "value"}})
 
         rendered = mock_toolset._render_headers()
+        # The header with missing key should still be in the result with unrendered template
+        assert rendered is not None
+        assert "X-Missing" in rendered
         assert "Header key 'X-Nonexistent' not found in request_context" in caplog.text
holmes/plugins/toolsets/mcp/toolset_mcp.py (2)

124-129: Move request_context assignment inside the lock to prevent race conditions.

The _request_context is instance state shared across concurrent requests. Setting it outside the lock allows multiple requests to overwrite each other's context before acquiring the per-server lock, causing wrong headers to be rendered. The assignment must be serialized inside the lock, with cleanup in a finally block.

🔒 Suggested fix
-            # Set request_context in toolset for header template rendering
-            self.toolset.set_request_context(context.request_context)
-
-            with lock:
-                return asyncio.run(self._invoke_async(params))
+            with lock:
+                # Set request_context in toolset for header template rendering
+                self.toolset.set_request_context(context.request_context)
+                try:
+                    return asyncio.run(self._invoke_async(params))
+                finally:
+                    self.toolset.set_request_context(None)

286-287: Use lambda replacements in re.sub to prevent data corruption.

re.sub() interprets backslashes and backreference patterns (\1, \n) in the replacement string. Header values from request context or environment variables can contain these characters, causing corruption or errors. Use a lambda to ensure literal replacement.

Suggested fix
-                result = re.sub(pattern_regex, replacement, result)
+                result = re.sub(pattern_regex, lambda _: replacement, result)

Apply to both occurrences at lines 287 and 300.

Also applies to: 299-300

🧹 Nitpick comments (1)
holmes/plugins/toolsets/mcp/toolset_mcp.py (1)

48-55: Add backward compatibility migration for headers → extra_headers rename.

Per coding guidelines, maintain backward compatibility when renaming config fields. Users with existing headers configuration will silently lose their settings. Add a @model_validator to migrate the old field name and log a deprecation warning.

Suggested approach
class MCPConfig(BaseModel):
    model_config = {"extra": "allow"}
    
    url: AnyUrl
    mode: MCPMode = MCPMode.SSE
    extra_headers: Optional[Dict[str, str]] = None
    
    `@model_validator`(mode="after")
    def migrate_headers(self) -> "MCPConfig":
        # Access extra fields via model_extra
        if hasattr(self, "model_extra") and self.model_extra and "headers" in self.model_extra:
            if self.extra_headers is None:
                self.extra_headers = self.model_extra["headers"]
                logging.warning(
                    "MCPConfig: 'headers' field is deprecated, use 'extra_headers' instead."
                )
        return self

Apply similar migration to StdioMCPConfig. Based on learnings.

Comment thread tests/test_mcp_toolset.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

🤖 Fix all issues with AI agents
In `@holmes/plugins/toolsets/mcp/toolset_mcp.py`:
- Around line 248-260: The bare except in the header-rendering loop should be
narrowed to the actual exceptions raised by _render_template to satisfy Ruff
BLE001: catch re.error, AttributeError, and TypeError instead of Exception (or
alternatively add a justified "# noqa: BLE001" comment); update the except
clause in the loop that iterates self._mcp_config.extra_headers in
toolset_mcp.py (referencing self._mcp_config, _render_template, and
final_headers) to catch re.error, AttributeError, and TypeError and keep the
existing logging inside the handler.

In `@server.py`:
- Around line 197-211: The current merge in investigate_issues lets
investigate_request.context override filtered passthrough headers by calling
request_context.update(investigate_request.context); change this to ignore any
"headers" key (or re-filter that nested headers map against BLOCKED_HEADERS)
before merging so body-supplied headers cannot bypass protections—modify the
investigate_issues handler to strip or validate
investigate_request.context.get("headers") (or remove the "headers" key) prior
to request_context.update(...) and ensure any remaining keys are safe to merge.
♻️ Duplicate comments (2)
server.py (1)

224-241: Same header-override risk here as in /api/investigate.

holmes/plugins/toolsets/mcp/toolset_mcp.py (1)

277-315: Use literal replacements in re.sub to avoid backslash/backref corruption.

Direct replacements interpret \1, \n, etc. in header/env values. Use a replacement function to keep values literal.

🐛 Proposed fix
-                    result = re.sub(pattern_regex, matching_value, result)
+                    result = re.sub(pattern_regex, lambda _m: matching_value, result)
...
-                result = re.sub(pattern_regex, replacement, result)
+                result = re.sub(pattern_regex, lambda _m: replacement, result)

Comment thread holmes/plugins/toolsets/mcp/toolset_mcp.py
Comment thread server.py
@gossion
gossion force-pushed the guwe/0121-ctx-header branch from 3b50ed6 to 45118c1 Compare January 21, 2026 10:28

@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

🤖 Fix all issues with AI agents
In `@tests/test_mcp_toolset.py`:
- Around line 1414-1437: Rename the unused callback parameters in
capture_sse_client_call by prefixing them with underscores (e.g., change url and
sse_read_timeout to _url and _sse_read_timeout) to satisfy Ruff ARG001; also
avoid assigning the session variable in the first test block by changing the
async context manager usage from "as session:" to "as _:" when calling
get_initialized_mcp_session in the run_test coroutine, and for the second
occurrence where mcp_tool._invoke_async is invoked without a context manager,
remove or rename any unused local variable named session to _ (or eliminate the
assignment) so no unused "session" identifier remains.
♻️ Duplicate comments (2)
holmes/plugins/toolsets/mcp/toolset_mcp.py (2)

259-271: Narrow the broad except Exception in header rendering (BLE001).

Catching only expected exceptions keeps behavior the same while satisfying Ruff.

♻️ Suggested fix
-                except Exception as e:
+                except (re.error, AttributeError, TypeError) as e:
                     logging.warning(
                         f"MCP toolset '{self.name}': Failed to render header template "
                         f"'{header_name}': {e}"
                     )

288-326: Use literal replacements in re.sub to avoid backreference surprises.

Header/env values containing backslashes or \1-like sequences can be corrupted by direct replacement strings.

♻️ Suggested fix
-                    result = re.sub(pattern_regex, matching_value, result)
+                    result = re.sub(
+                        pattern_regex, lambda _m: str(matching_value), result
+                    )
@@
-                result = re.sub(pattern_regex, replacement, result)
+                result = re.sub(pattern_regex, lambda _m: replacement, result)
🧹 Nitpick comments (1)
holmes/core/tool_calling_llm.py (1)

279-313: Avoid constructing DummySpan() in default args.

Default args are evaluated once; use None and instantiate inside to satisfy Ruff B008 and avoid shared instances. Also apply the same pattern to other entrypoints for consistency.

♻️ Suggested refactor
 def prompt_call(
     self,
     system_prompt: str,
     user_prompt: str,
     response_format: Optional[Union[dict, Type[BaseModel]]] = None,
     sections: Optional[InputSectionsDataType] = None,
-    trace_span=DummySpan(),
+    trace_span: Optional[DummySpan] = None,
     request_context: Optional[Dict[str, Any]] = None,
 ) -> LLMResult:
+    trace_span = trace_span or DummySpan()
     messages = [
         {"role": "system", "content": system_prompt},
         {"role": "user", "content": user_prompt},
     ]
     return self.call(
         messages,
         response_format=response_format,
         user_prompt=user_prompt,
         sections=sections,
         trace_span=trace_span,
         request_context=request_context,
     )

 def messages_call(
     self,
     messages: List[Dict[str, str]],
     response_format: Optional[Union[dict, Type[BaseModel]]] = None,
-    trace_span=DummySpan(),
+    trace_span: Optional[DummySpan] = None,
     request_context: Optional[Dict[str, Any]] = None,
 ) -> LLMResult:
+    trace_span = trace_span or DummySpan()
     return self.call(
         messages,
         response_format=response_format,
         trace_span=trace_span,
         request_context=request_context,
     )

Comment thread tests/test_mcp_toolset.py
@gossion
gossion force-pushed the guwe/0121-ctx-header branch 2 times, most recently from 02e5f64 to bb9dcdb Compare January 21, 2026 10:51
@gossion
gossion marked this pull request as ready for review January 21, 2026 10:52
@gossion
gossion force-pushed the guwe/0121-ctx-header branch 2 times, most recently from 5e6e631 to de87af3 Compare January 23, 2026 01:04
arikalon1
arikalon1 previously approved these changes Jan 25, 2026

@arikalon1 arikalon1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nice work @gossion !

Left 2 small comments.

In addition, can we add a little note on this on the MCP docs? Might be useful for other users as well. (https://holmesgpt.dev/data-sources/remote-mcp-servers/#1-run-stdio-mcp-as-http-endpoint)

Comment thread holmes/plugins/toolsets/mcp/toolset_mcp.py
Comment thread 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: 0

Caution

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

⚠️ Outside diff range comments (1)
holmes/core/tool_calling_llm.py (1)

863-872: Missing request_context in the "approval no longer needed" re-invocation path.

When approval is no longer needed and the tool is re-invoked at lines 864-870, request_context is not passed to _directly_invoke_tool_call. This is inconsistent with the "user approved" branch at line 892 which correctly passes request_context. MCP headers won't be rendered for this code path.

🐛 Proposed fix
             with trace_span.start_span(type="tool") as tool_span:
                 tool_call_result.result = self._directly_invoke_tool_call(
                     tool_name=tool_call_result.tool_name,
                     tool_params=tool_call_result.result.params or {},
                     user_approved=False,
                     tool_number=tool_number,
                     tool_call_id=tool_call_result.tool_call_id,
+                    request_context=request_context,
                 )
                 ToolCallingLLM._log_tool_call_result(tool_span, tool_call_result)
🧹 Nitpick comments (2)
holmes/plugins/toolsets/mcp/toolset_mcp.py (1)

390-397: Add # noqa: BLE001 for consistency with line 355.

The exception handling here follows the same pattern as _render_headers (line 355) where a broad Exception catch is used as a fail-safe during template rendering. For consistency, add the noqa comment to suppress the Ruff warning.

♻️ Suggested fix
-        except Exception as e:
+        except Exception as e:  # noqa: BLE001
tests/test_mcp_toolset.py (1)

1416-1422: Prefix unused sse_read_timeout parameter with underscore.

The _url parameter is correctly prefixed, but sse_read_timeout is also unused and should follow the same convention to satisfy Ruff ARG001.

♻️ Suggested fix
-        def capture_sse_client_call(_url, headers, *, sse_read_timeout):
+        def capture_sse_client_call(_url, headers, *, _sse_read_timeout):

Also applies to line 1493.

@gossion

gossion commented Jan 26, 2026

Copy link
Copy Markdown
Contributor Author

had some conflicts, I fixup all commits and rebased it.

@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

🤖 Fix all issues with AI agents
In `@docs/data-sources/remote-mcp-servers.md`:
- Around line 53-73: Update the docs for mcp_servers to state that both headers
and extra_headers may be defined together and are merged for the outbound MCP
request, with extra_headers taking precedence on name collisions; specifically
mention the fields shown in the snippet (mcp_servers -> my_server -> config ->
headers and extra_headers) and that template variables like
request_context.headers[...] in extra_headers will override any same-named keys
from headers when the final request headers are constructed.
🧹 Nitpick comments (1)
docs/data-sources/remote-mcp-servers.md (1)

93-97: Avoid secret-scanner false positives in curl example.
Even placeholder tokens can trigger scanners. Consider using an env var placeholder to keep docs clean.

♻️ Proposed doc tweak
-curl -X POST http://holmes-server/api/investigate \
-  -H "X-Auth-Token: your-auth-token-here" \
+curl -X POST http://holmes-server/api/investigate \
+  -H "X-Auth-Token: ${X_AUTH_TOKEN}" \
   -H "Content-Type: application/json" \
   -d '{"question": "Check system status"}'

Comment thread docs/data-sources/remote-mcp-servers.md

@mainred mainred left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This PR overall looks good to me. Thanks.

Comment thread server.py
Comment thread holmes/core/tools.py
@gossion
gossion force-pushed the guwe/0121-ctx-header branch from cff758d to 7815abc Compare January 26, 2026 03:50
mainred
mainred previously approved these changes Jan 26, 2026
arikalon1
arikalon1 previously approved these changes Jan 26, 2026

@arikalon1 arikalon1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nice work!

@linux-foundation-easycla

linux-foundation-easycla Bot commented Jan 26, 2026 •

Copy link
Copy Markdown

CLA Signed

The committers listed above are authorized under a signed CLA.

Signed-off-by: Guoxun Wei <guwe@microsoft.com>
Signed-off-by: Guoxun Wei <guwe@microsoft.com>
Signed-off-by: Guoxun Wei <guwe@microsoft.com>
@gossion
gossion dismissed stale reviews from mainred and arikalon1 via d38842c January 26, 2026 13:19
@gossion
gossion force-pushed the guwe/0121-ctx-header branch from 089cf34 to d38842c Compare January 26, 2026 13:19

@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)
holmes/core/tool_calling_llm.py (1)

858-870: Propagate request_context when approval is already satisfied.

The fast-path re-invocation skips request_context, so MCP headers can be missing for auto-approved tools.

🐛 Suggested fix
                 tool_call_result.result = self._directly_invoke_tool_call(
                     tool_name=tool_call_result.tool_name,
                     tool_params=tool_call_result.result.params or {},
                     user_approved=False,
                     tool_number=tool_number,
                     tool_call_id=tool_call_result.tool_call_id,
+                    request_context=request_context,
                 )
🤖 Fix all issues with AI agents
In `@docs/data-sources/remote-mcp-servers.md`:
- Around line 91-97: Replace the literal header value in the curl example to
avoid gitleaks false positives: update the "X-Auth-Token: your-auth-token-here"
header to use a clearly-placeholder variable (e.g., X-Auth-Token: ${AUTH_TOKEN}
or X-Auth-Token: <AUTH_TOKEN>) in the HolmesGPT request example so scanners
won’t treat it as a real secret; modify the example that constructs the curl
POST to /api/investigate accordingly.

In `@holmes/plugins/toolsets/mcp/toolset_mcp.py`:
- Around line 390-397: The current broad except in the template rendering block
should be narrowed to catch Jinja2-specific errors: replace the generic "except
Exception as e" around Template(template_str) / template.render(context) with
"except jinja2.TemplateError as e" (or "except TemplateError as e" if
TemplateError is imported) so only Jinja2 template errors are handled; keep the
logging using self.name and template_str and return template_str on that
specific except. Ensure jinja2.TemplateError is imported or referenced fully.

In `@tests/test_mcp_toolset.py`:
- Around line 1418-1421: The local callback function capture_sse_client_call
defines unused parameters sse_read_timeout and httpx_client_factory; rename them
to _sse_read_timeout and _httpx_client_factory (and do the same in the other
occurrence at the second instance) so they are prefixed with underscores to
satisfy the Ruff ARG rule while keeping the function body unchanged; update the
function signature in capture_sse_client_call accordingly wherever it appears.
♻️ Duplicate comments (1)
docs/data-sources/remote-mcp-servers.md (1)

49-73: Clarify that extra_headers can be used alongside headers.

The text currently says “instead of headers”, but the implementation merges both (with extra_headers taking precedence).

✏️ Suggested wording
-Use the `extra_headers` field (instead of `headers`) with template variables to reference headers from the incoming request:
+Use `extra_headers` for templated values, optionally alongside `headers` for static values. When both are present, `extra_headers` takes precedence on key collisions:
🧹 Nitpick comments (1)
tests/test_mcp_toolset.py (1)

1145-1329: Consider stubbing _get_server_tools in header-rendering unit tests.

Calling prerequisites_callable can attempt real network I/O; using a local stub (or setting _mcp_config directly) keeps these tests deterministic and fast.

Comment thread docs/data-sources/remote-mcp-servers.md
Comment thread holmes/plugins/toolsets/mcp/toolset_mcp.py
Comment thread tests/test_mcp_toolset.py
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