Skip to content

fix(secops): log investigation diagnostics instead of printing - #315

Open
abdeltaehass wants to merge 1 commit into
google:mainfrom
abdeltaehass:fix/investigations-stdout-logging
Open

fix(secops): log investigation diagnostics instead of printing#315
abdeltaehass wants to merge 1 commit into
google:mainfrom
abdeltaehass:fix/investigations-stdout-logging

Conversation

@abdeltaehass

Copy link
Copy Markdown
Contributor

Fixes #267

investigation_management.py reports progress and errors with print(). On
the MCP stdio transport stdout is the JSON-RPC channel, so those writes go
into the protocol stream.

The issue points at three calls in list_investigations. There are twelve,
across all four tools in the file — list_investigations, get_investigation,
trigger_investigation and fetch_associated_investigations. This routes all
of them through the module logger the file already defines at line 22.

Why it breaks the stream

mcp/server/stdio.py wraps sys.stdout.buffer in its own TextIOWrapper
instead of replacing sys.stdout, so print() and the JSON-RPC encoder hold
separate buffers on fd 1. When stdout is a pipe, sys.stdout block-buffers at
8KB and flushes whenever it fills, which is usually mid-response.

Driving the server over stdio with 200 list_investigations calls:

stdout lines valid JSON-RPC unparseable
before 272 108 164
after 101 101 0

One call won't show it — the message sits in the buffer and gets dropped at
exit, which also means these diagnostics currently reach nobody. Sending them
through the logger puts them on stderr.

Changes

  • 8 progress print() to logger.info(), message text unchanged.
  • 4 print(error_msg) to logger.error(error_msg, exc_info=True), which keeps
    the traceback. logger.error(..., exc_info=True) is used 38 times across
    these tools and logger.exception isn't used at all, so this matches.
  • Three lines rewrapped for the file's 79 column limit.

Return values are untouched.

Tests

server/secops/tests/test_investigation_management_unit.py, following the
patch + MagicMock fixture style in test_security_alerts_unit.py. Success
and failure path per tool, eight tests. Each asserts stdout is empty, checks
the full sequence of (level, message) pairs from the logger, and on the failure
path checks the record carries exc_info for the right exception type.

$ python -m pytest tests/test_investigation_management_unit.py -q
8 passed

All eight fail against the current code. The full server/secops suite goes
from 25 to 33 passing; the 52 errors are pre-existing and unrelated, they're
integration tests wanting a tests/config.json with live credentials.

ruff check --isolated --select E9,F63,F7,F82,F401,T201 and --select E501 --line-length 79 are clean on both files.

Out of scope

server/secops-soar/secops_soar_mcp/marketplace/ has the same pattern in bulk
(~6.4k calls over 294 files), but those look generated, so they're better
handled at the generator than by hand here.

…e#267)

stdout is the JSON-RPC channel on the MCP stdio transport, but all four
tools in investigation_management.py wrote progress and error messages
there with print(). Every other tool in the package already uses the
module logger defined at the top of this file.

The SDK wraps sys.stdout.buffer in its own TextIOWrapper rather than
replacing sys.stdout, so print() and the JSON-RPC writer end up holding
separate buffers on the same fd. sys.stdout block-buffers at 8KB when
stdout is a pipe and flushes once it fills, which lands in the middle of
a response. Running the server over stdio with 200 tool calls, 164 of
272 stdout lines came back unparseable; none do now. A single call never
shows this, since the message just sits in the buffer and is dropped at
exit, so today these messages reach nobody at all.

Swap the 12 print() calls for logger.info(), and logger.error(...,
exc_info=True) on the error paths to match the rest of the package.
Message text is unchanged. Tests cover the success and failure path of
each tool: stdout stays empty, the expected messages are logged at the
expected level, and the error records carry the traceback.
@abdeltaehass
abdeltaehass requested a review from a team September 10, 2026 16:38
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.

secops_mcp_list_investigations_print_to_stdout_bug

1 participant