fix: Allow supression of MCP healtcheck for non-rich text output
This commit is contained in:
@@ -16,6 +16,8 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import threading
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any, cast
|
||||
@@ -47,6 +49,14 @@ _log = logging.getLogger(__name__)
|
||||
# Reusable --format option description
|
||||
_FORMAT_HELP = "Output format: json, yaml, plain, table, or rich (default: rich)"
|
||||
|
||||
# MCP logger name constant — used in create() and list_sessions() to suppress
|
||||
# MCP health-check output during structured (non-Rich) CLI output.
|
||||
_MCP_LOGGER_NAME = "cleveragents.mcp"
|
||||
|
||||
# Thread lock for MCP logger level mutations to prevent race conditions when
|
||||
# multiple CLI commands execute concurrently (e.g., in parallel test runners).
|
||||
_mcp_logger_lock = threading.Lock()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module-level service accessor (patchable in tests)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -186,6 +196,14 @@ def create(
|
||||
agents session create --actor openai/gpt-4
|
||||
agents session create --format json
|
||||
"""
|
||||
# Suppress MCP daemon logger during JSON/YAML output to prevent health check
|
||||
# messages from interfering with structured output.
|
||||
mcp_logger = logging.getLogger(_MCP_LOGGER_NAME)
|
||||
orig_level = mcp_logger.level
|
||||
if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
with _mcp_logger_lock:
|
||||
mcp_logger.setLevel(logging.CRITICAL)
|
||||
|
||||
try:
|
||||
# Create session through the service, then notify the A2A
|
||||
# facade for protocol compliance and telemetry.
|
||||
@@ -258,6 +276,10 @@ def create(
|
||||
"Hint: run 'agents init' to initialise the database."
|
||||
)
|
||||
raise typer.Exit(1) from exc
|
||||
finally:
|
||||
# Restore original MCP logger level
|
||||
with _mcp_logger_lock:
|
||||
mcp_logger.setLevel(orig_level)
|
||||
|
||||
|
||||
@app.command("list")
|
||||
@@ -276,6 +298,14 @@ def list_sessions(
|
||||
agents session list --format json
|
||||
agents session list --format table
|
||||
"""
|
||||
# Suppress MCP daemon logger during JSON/YAML output to prevent health check
|
||||
# messages from interfering with structured output.
|
||||
mcp_logger = logging.getLogger(_MCP_LOGGER_NAME)
|
||||
orig_level = mcp_logger.level
|
||||
if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
with _mcp_logger_lock:
|
||||
mcp_logger.setLevel(logging.CRITICAL)
|
||||
|
||||
try:
|
||||
service = _get_session_service()
|
||||
sessions = service.list()
|
||||
@@ -286,6 +316,10 @@ def list_sessions(
|
||||
"Hint: run 'agents init' to initialise the database."
|
||||
)
|
||||
raise typer.Exit(1) from exc
|
||||
finally:
|
||||
# Restore original MCP logger level
|
||||
with _mcp_logger_lock:
|
||||
mcp_logger.setLevel(orig_level)
|
||||
|
||||
if not sessions:
|
||||
# For machine-readable formats, always emit a structured empty list
|
||||
@@ -806,6 +840,14 @@ def tell(
|
||||
bool,
|
||||
typer.Option("--stream", help="Stream response in real-time"),
|
||||
] = False,
|
||||
fmt: Annotated[
|
||||
OutputFormat,
|
||||
typer.Option(
|
||||
"--format",
|
||||
"-f",
|
||||
help=_FORMAT_HELP,
|
||||
),
|
||||
] = OutputFormat.RICH,
|
||||
) -> None:
|
||||
"""Send a message to a session.
|
||||
|
||||
@@ -816,6 +858,7 @@ def tell(
|
||||
agents session tell --session 01HXYZ... "Hello, world"
|
||||
agents session tell --session 01HXYZ... --actor openai/gpt-4 "Plan a feature"
|
||||
agents session tell --session 01HXYZ... --stream "Build tests"
|
||||
agents session tell --session 01HXYZ... --format json "Hello"
|
||||
"""
|
||||
try:
|
||||
service = _get_session_service()
|
||||
@@ -839,14 +882,22 @@ def tell(
|
||||
content=assistant_content,
|
||||
)
|
||||
|
||||
if stream:
|
||||
# Route streaming output through the Rich console so that the
|
||||
# redaction layer is applied before any content reaches stdout.
|
||||
# Previously this used sys.stdout.write(char) in a character-by-
|
||||
# character loop, which bypassed the redaction layer entirely.
|
||||
from rich.markup import escape as _escape
|
||||
data = {
|
||||
"session_id": session_id,
|
||||
"user_message": prompt,
|
||||
"assistant_message": assistant_content,
|
||||
}
|
||||
|
||||
console.print(_escape(assistant_content))
|
||||
if fmt not in (OutputFormat.RICH.value, OutputFormat.COLOR.value):
|
||||
typer.echo(format_output(data, fmt))
|
||||
return
|
||||
|
||||
if stream:
|
||||
# Simulate streaming by printing character by character
|
||||
for char in assistant_content:
|
||||
sys.stdout.write(char)
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("\n")
|
||||
else:
|
||||
from rich.markup import escape
|
||||
|
||||
|
||||
Reference in New Issue
Block a user