Skip to content

Commit 3e2b64f

Browse files
committed
feat: logging control through MCP_REDIS_LOG_LEVEL env var
1 parent 74c074c commit 3e2b64f

File tree

8 files changed

+290
-32
lines changed

8 files changed

+290
-32
lines changed

README.md

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ The Redis MCP Server is a **natural language interface** designed for agentic ap
3737
- [Redis ACL](#redis-acl)
3838
- [Configuration via command line arguments](#configuration-via-command-line-arguments)
3939
- [Configuration via Environment Variables](#configuration-via-environment-variables)
40+
- [Logging](#logging)
4041
- [Integrations](#integrations)
4142
- [OpenAI Agents SDK](#openai-agents-sdk)
4243
- [Augment](#augment)
@@ -78,7 +79,7 @@ Additional tools.
7879

7980
## Installation
8081

81-
The Redis MCP Server is available as a PyPI package and as direct installation from the GitHub repository.
82+
The Redis MCP Server is available as a PyPI package and as direct installation from the GitHub repository.
8283

8384
### From PyPI (recommended)
8485
Configuring the latest Redis MCP Server version from PyPI, as an example, can be done importing the following JSON configuration in the desired framework or tool.
@@ -125,7 +126,7 @@ However, starting the MCP Server is most useful when delegate to the framework o
125126

126127
You can configure the desired Redis MCP Server version with `uvx`, which allows you to run it directly from GitHub (from a branch, or use a tagged release).
127128

128-
> It is recommended to use a tagged release, the `main` branch is under active development and may contain breaking changes.
129+
> It is recommended to use a tagged release, the `main` branch is under active development and may contain breaking changes.
129130
130131
As an example, you can execute the following command to run the `0.2.0` release:
131132

@@ -140,7 +141,7 @@ Additional examples are provided below.
140141
# Run with Redis URI
141142
uvx --from git+https://github.com/redis/mcp-redis.git redis-mcp-server --url redis://localhost:6379/0
142143

143-
# Run with Redis URI and SSL
144+
# Run with Redis URI and SSL
144145
uvx --from git+https://github.com/redis/mcp-redis.git redis-mcp-server --url "rediss://<USERNAME>:<PASSWORD>@<HOST>:<PORT>?ssl_cert_reqs=required&ssl_ca_certs=<PATH_TO_CERT>"
145146

146147
# Run with individual parameters
@@ -318,7 +319,7 @@ If desired, you can use environment variables. Defaults are provided for all var
318319

319320
There are several ways to set environment variables:
320321

321-
1. **Using a `.env` File**:
322+
1. **Using a `.env` File**:
322323
Place a `.env` file in your project directory with key-value pairs for each environment variable. Tools like `python-dotenv`, `pipenv`, and `uv` can automatically load these variables when running your application. This is a convenient and secure way to manage configuration, as it keeps sensitive data out of your shell history and version control (if `.env` is in `.gitignore`).
323324
For example, create a `.env` file with the following content from the `.env.example` file provided in the repository:
324325

@@ -330,7 +331,7 @@ Then edit the `.env` file to set your Redis configuration:
330331

331332
OR,
332333

333-
2. **Setting Variables in the Shell**:
334+
2. **Setting Variables in the Shell**:
334335
You can export environment variables directly in your shell before running your application. For example:
335336

336337
```sh
@@ -342,6 +343,46 @@ export REDIS_PORT=6379
342343
This method is useful for temporary overrides or quick testing.
343344

344345

346+
### Logging
347+
348+
The server uses Python's standard logging and is configured at startup. By default it logs at WARNING and above. You can change verbosity with the `MCP_REDIS_LOG_LEVEL` environment variable.
349+
350+
- Accepted values (case-insensitive): `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`, `NOTSET`
351+
- Aliases supported: `WARN``WARNING`, `FATAL``CRITICAL`
352+
- Numeric values are also accepted, including signed (e.g., `"10"`, `"+20"`)
353+
- Default when unset or unrecognized: `WARNING`
354+
355+
Handler behavior
356+
- If the host (e.g., `uv`, VS Code, pytest) already installed console handlers, the server will NOT add its own; it only lowers overly-restrictive handler thresholds so your chosen level is not filtered out. It will never raise a handler's threshold.
357+
- If no handlers are present, the server adds a single stderr StreamHandler with a simple format.
358+
359+
Examples
360+
```bash
361+
# See normal lifecycle messages
362+
MCP_REDIS_LOG_LEVEL=INFO uv run src/main.py
363+
364+
# Very verbose for debugging
365+
MCP_REDIS_LOG_LEVEL=DEBUG uvx --from redis-mcp-server@latest redis-mcp-server --url redis://localhost:6379/0
366+
```
367+
368+
In MCP client configs that support env, add it alongside your Redis settings. For example:
369+
```json
370+
{
371+
"mcpServers": {
372+
"redis": {
373+
"command": "uvx",
374+
"args": ["--from", "redis-mcp-server@latest", "redis-mcp-server", "--url", "redis://localhost:6379/0"],
375+
"env": {
376+
"REDIS_HOST": "localhost",
377+
"REDIS_PORT": "6379",
378+
"MCP_REDIS_LOG_LEVEL": "INFO"
379+
}
380+
}
381+
}
382+
}
383+
```
384+
385+
345386
## Integrations
346387

347388
Integrating this MCP Server to development frameworks like OpenAI Agents SDK, or with tools like Claude Desktop, VS Code, or Augment is described in the following sections.
@@ -439,7 +480,7 @@ You can start the GitHub desired version of the Redis MCP server using `uvx` by
439480
"servers": {
440481
"Redis MCP Server": {
441482
"type": "stdio",
442-
"command": "uvx",
483+
"command": "uvx",
443484
"args": [
444485
"--from", "redis-mcp-server@latest",
445486
"redis-mcp-server",

src/common/connection.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import sys
1+
import logging
22
from typing import Optional, Type, Union
33

44
import redis
@@ -8,6 +8,8 @@
88
from src.common.config import REDIS_CFG
99
from src.version import __version__
1010

11+
_logger = logging.getLogger(__name__)
12+
1113

1214
class RedisConnectionManager:
1315
_instance: Optional[Redis] = None
@@ -57,25 +59,25 @@ def get_connection(cls, decode_responses=True) -> Redis:
5759
cls._instance = redis_class(**connection_params)
5860

5961
except redis.exceptions.ConnectionError:
60-
print("Failed to connect to Redis server", file=sys.stderr)
62+
_logger.error("Failed to connect to Redis server")
6163
raise
6264
except redis.exceptions.AuthenticationError:
63-
print("Authentication failed", file=sys.stderr)
65+
_logger.error("Authentication failed")
6466
raise
6567
except redis.exceptions.TimeoutError:
66-
print("Connection timed out", file=sys.stderr)
68+
_logger.error("Connection timed out")
6769
raise
6870
except redis.exceptions.ResponseError as e:
69-
print(f"Response error: {e}", file=sys.stderr)
71+
_logger.error("Response error: %s", e)
7072
raise
7173
except redis.exceptions.RedisError as e:
72-
print(f"Redis error: {e}", file=sys.stderr)
74+
_logger.error("Redis error: %s", e)
7375
raise
7476
except redis.exceptions.ClusterError as e:
75-
print(f"Redis Cluster error: {e}", file=sys.stderr)
77+
_logger.error("Redis Cluster error: %s", e)
7678
raise
7779
except Exception as e:
78-
print(f"Unexpected error: {e}", file=sys.stderr)
80+
_logger.error("Unexpected error: %s", e)
7981
raise
8082

8183
return cls._instance

src/common/logging_utils.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import logging
2+
import os
3+
import sys
4+
5+
6+
def resolve_log_level() -> int:
7+
"""Resolve desired log level from MCP_REDIS_LOG_LEVEL.
8+
9+
Accepts numeric strings or standard level names (DEBUG, INFO, WARNING,
10+
ERROR, CRITICAL, NOTSET) including aliases WARN and FATAL. Defaults to WARNING.
11+
"""
12+
name = os.getenv("MCP_REDIS_LOG_LEVEL")
13+
if name:
14+
s = name.strip()
15+
try:
16+
return int(s)
17+
except ValueError:
18+
pass
19+
level = getattr(logging, s.upper(), None)
20+
if isinstance(level, int):
21+
return level
22+
return logging.WARNING
23+
24+
25+
def configure_logging() -> int:
26+
"""Configure logging based on environment.
27+
28+
- Default level WARNING
29+
- MCP_REDIS_LOG_LEVEL to override
30+
31+
Returns the resolved log level. Idempotent.
32+
"""
33+
34+
level = resolve_log_level()
35+
root = logging.getLogger()
36+
37+
# Always set the root logger level
38+
root.setLevel(level)
39+
40+
# Only lower overly-restrictive handler thresholds to avoid host filtering.
41+
# - Leave NOTSET (0) alone so it defers to logger/root levels
42+
# - Do not raise handler thresholds (respect host-configured verbosity)
43+
for h in root.handlers:
44+
try:
45+
cur = getattr(h, "level", None)
46+
if isinstance(cur, int) and cur != logging.NOTSET and cur > level:
47+
h.setLevel(level)
48+
except Exception:
49+
pass
50+
51+
# Only add our own stderr handler if there are NO handlers at all.
52+
# Many hosts (pytest, uv, VS Code) install a console handler already.
53+
if not root.handlers:
54+
sh = logging.StreamHandler(sys.stderr)
55+
sh.setLevel(level)
56+
sh.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
57+
root.addHandler(sh)
58+
59+
# Route warnings.warn(...) through logging
60+
logging.captureWarnings(True)
61+
62+
return level

src/main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import sys
2+
import logging
23

34
import click
45

56
from src.common.config import parse_redis_uri, set_redis_config_from_cli
67
from src.common.server import mcp
8+
from src.common.logging_utils import configure_logging
79

810

911
class RedisMCPServer:
1012
def __init__(self):
11-
print("Starting the Redis MCP Server", file=sys.stderr)
13+
# Configure logging on server initialization (idempotent)
14+
configure_logging()
15+
self._logger = logging.getLogger(__name__)
16+
self._logger.info("Starting the Redis MCP Server")
1217

1318
def run(self):
1419
mcp.run()

tests/test_integration.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,9 @@ def test_server_lists_tools(self, server_process):
240240
"get",
241241
]
242242
for tool in tool_names:
243-
assert tool in expected_tools, (
244-
f"Expected tool '{tool}' not found in {tool_names}"
245-
)
243+
assert (
244+
tool in expected_tools
245+
), f"Expected tool '{tool}' not found in {tool_names}"
246246

247247
def test_server_tool_count_and_names(self, server_process):
248248
"""Test that the server registers the correct number of tools with expected names."""
@@ -269,9 +269,9 @@ def test_server_tool_count_and_names(self, server_process):
269269

270270
# Expected tool count (based on @mcp.tool() decorators in codebase)
271271
expected_tool_count = 44
272-
assert len(tools) == expected_tool_count, (
273-
f"Expected {expected_tool_count} tools, but got {len(tools)}"
274-
)
272+
assert (
273+
len(tools) == expected_tool_count
274+
), f"Expected {expected_tool_count} tools, but got {len(tools)}"
275275

276276
# Expected tool names (alphabetically sorted for easier verification)
277277
expected_tools = [
@@ -360,9 +360,9 @@ def test_server_tool_count_and_names(self, server_process):
360360

361361
for category, category_tools in tool_categories.items():
362362
for tool in category_tools:
363-
assert tool in tool_names, (
364-
f"Tool '{tool}' from category '{category}' not found in registered tools"
365-
)
363+
assert (
364+
tool in tool_names
365+
), f"Tool '{tool}' from category '{category}' not found in registered tools"
366366

367367
def _initialize_server(self, server_process):
368368
"""Helper to initialize the MCP server."""

0 commit comments

Comments
 (0)