Skip to content

Commit 47ace88

Browse files
committed
Stop logging to sdtio with mcp server in stdio mode
1 parent 2647e0a commit 47ace88

File tree

4 files changed

+64
-18
lines changed

4 files changed

+64
-18
lines changed

agent_memory_server/cli.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55

66
import datetime
77
import importlib
8-
import logging
98
import sys
109

1110
import click
1211
import uvicorn
1312

1413
from agent_memory_server.config import settings
15-
from agent_memory_server.logging import configure_logging, get_logger
14+
from agent_memory_server.logging import (
15+
configure_logging,
16+
configure_mcp_logging,
17+
get_logger,
18+
)
1619
from agent_memory_server.migrations import (
1720
migrate_add_discrete_memory_extracted_2,
1821
migrate_add_memory_hashes_1,
@@ -21,7 +24,7 @@
2124
from agent_memory_server.utils.redis import ensure_search_index_exists, get_redis_conn
2225

2326

24-
configure_logging()
27+
# Don't configure logging at module level - let each command handle it
2528
logger = get_logger(__name__)
2629

2730
VERSION = "0.2.0"
@@ -44,6 +47,8 @@ def rebuild_index():
4447
"""Rebuild the search index."""
4548
import asyncio
4649

50+
configure_logging()
51+
4752
async def setup_and_run():
4853
redis = await get_redis_conn()
4954
await ensure_search_index_exists(redis, overwrite=True)
@@ -56,6 +61,7 @@ def migrate_memories():
5661
"""Migrate memories from the old format to the new format."""
5762
import asyncio
5863

64+
configure_logging()
5965
click.echo("Starting memory migrations...")
6066

6167
async def run_migrations():
@@ -81,6 +87,7 @@ def api(port: int, host: str, reload: bool):
8187
"""Run the REST API server."""
8288
from agent_memory_server.main import on_start_logger
8389

90+
configure_logging()
8491
on_start_logger(port)
8592
uvicorn.run(
8693
"agent_memory_server.main:app",
@@ -102,6 +109,12 @@ def mcp(port: int, mode: str):
102109
"""Run the MCP server."""
103110
import asyncio
104111

112+
# Configure MCP-specific logging BEFORE any imports to avoid stdout contamination
113+
if mode == "stdio":
114+
configure_mcp_logging()
115+
else:
116+
configure_logging()
117+
105118
# Update the port in settings FIRST
106119
settings.mcp_port = port
107120

@@ -116,14 +129,7 @@ async def setup_and_run():
116129
logger.info(f"Starting MCP server on port {port}\n")
117130
await mcp_app.run_sse_async()
118131
elif mode == "stdio":
119-
# Try to force all logging to stderr because stdio-mode MCP servers
120-
# use standard output for the protocol.
121-
logging.basicConfig(
122-
level=settings.log_level,
123-
stream=sys.stderr,
124-
force=True, # remove any existing handlers
125-
format="%(asctime)s %(name)s %(levelname)s %(message)s",
126-
)
132+
# Logging already configured above
127133
await mcp_app.run_stdio_async()
128134
else:
129135
raise ValueError(f"Invalid mode: {mode}")
@@ -153,6 +159,8 @@ def schedule_task(task_path: str, args: list[str]):
153159

154160
from docket import Docket
155161

162+
configure_logging()
163+
156164
# Parse the arguments
157165
task_args = {}
158166
for arg in args:
@@ -218,6 +226,8 @@ def task_worker(concurrency: int, redelivery_timeout: int):
218226

219227
from docket import Worker
220228

229+
configure_logging()
230+
221231
if not settings.use_docket:
222232
click.echo("Docket is disabled in settings. Cannot run worker.")
223233
sys.exit(1)

agent_memory_server/logging.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def configure_logging():
1717

1818
# Configure standard library logging based on settings.log_level
1919
level = getattr(logging, settings.log_level.upper(), logging.INFO)
20-
handler = logging.StreamHandler(sys.stdout)
20+
handler = logging.StreamHandler(sys.stderr) # Use stderr instead of stdout
2121
handler.setLevel(level)
2222
logging.basicConfig(level=level, handlers=[handler], format="%(message)s")
2323

@@ -38,6 +38,41 @@ def configure_logging():
3838
_configured = True
3939

4040

41+
def configure_mcp_logging():
42+
"""Configure logging specifically for MCP server in stdio mode"""
43+
global _configured
44+
45+
# Clear any existing handlers and configuration
46+
root_logger = logging.getLogger()
47+
root_logger.handlers.clear()
48+
49+
# Configure stderr-only logging for MCP stdio mode
50+
level = getattr(logging, settings.log_level.upper(), logging.INFO)
51+
stderr_handler = logging.StreamHandler(sys.stderr)
52+
stderr_handler.setLevel(level)
53+
stderr_handler.setFormatter(
54+
logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
55+
)
56+
root_logger.addHandler(stderr_handler)
57+
root_logger.setLevel(level)
58+
59+
# Configure structlog to also use stderr
60+
structlog.configure(
61+
processors=[
62+
structlog.stdlib.filter_by_level,
63+
structlog.stdlib.add_logger_name,
64+
structlog.stdlib.add_log_level,
65+
structlog.processors.TimeStamper(fmt="iso"),
66+
structlog.processors.format_exc_info,
67+
structlog.processors.JSONRenderer(),
68+
],
69+
wrapper_class=structlog.stdlib.BoundLogger,
70+
logger_factory=structlog.stdlib.LoggerFactory(),
71+
cache_logger_on_first_use=True,
72+
)
73+
_configured = True
74+
75+
4176
def get_logger(name: str | None = None) -> structlog.stdlib.BoundLogger:
4277
"""
4378
Get a configured logger instance.

agent_memory_server/vectorstore_factory.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ def patched_create_ulid() -> str:
3838

3939
# Replace the broken function with our working one
4040
redisvl.utils.utils.create_ulid = patched_create_ulid
41-
logging.info("Successfully patched RedisVL ULID function")
42-
except Exception as e:
43-
logging.warning(f"Could not patch RedisVL ULID function: {e}")
41+
# Note: Successfully patched RedisVL ULID function
42+
except Exception:
43+
# Note: Could not patch RedisVL ULID function
44+
pass
4445

4546
from agent_memory_server.config import settings
4647
from agent_memory_server.vectorstore_adapter import (

tests/test_cli.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,11 @@ def test_mcp_command_sse_mode(self, mock_mcp_app, mock_settings):
157157
assert result.exit_code == 0
158158
mock_mcp_app.run_sse_async.assert_called_once()
159159

160-
@patch("agent_memory_server.cli.logging.basicConfig")
160+
@patch("agent_memory_server.cli.configure_mcp_logging")
161161
@patch("agent_memory_server.cli.settings")
162162
@patch("agent_memory_server.mcp.mcp_app")
163163
def test_mcp_command_stdio_logging_config(
164-
self, mock_mcp_app, mock_settings, mock_basic_config
164+
self, mock_mcp_app, mock_settings, mock_configure_mcp_logging
165165
):
166166
"""Test that stdio mode configures logging to stderr."""
167167
mock_settings.mcp_port = 3001
@@ -174,7 +174,7 @@ def test_mcp_command_stdio_logging_config(
174174

175175
assert result.exit_code == 0
176176
mock_mcp_app.run_stdio_async.assert_called_once()
177-
mock_basic_config.assert_called_once()
177+
mock_configure_mcp_logging.assert_called_once()
178178

179179

180180
class TestScheduleTask:

0 commit comments

Comments
 (0)