Skip to content

Commit 8881af8

Browse files
authored
Merge pull request #58 from redis/feat/logging-config
feat: logging control through MCP_REDIS_LOG_LEVEL env var
2 parents e45d5d3 + 8fdeca9 commit 8881af8

File tree

7 files changed

+302
-30
lines changed

7 files changed

+302
-30
lines changed

README.md

Lines changed: 46 additions & 5 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.

examples/redis_assistant.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ async def build_agent():
1212
params={
1313
"command": "uv",
1414
"args": [
15-
"--directory", "../src/", # change with the path to the MCP server
16-
"run", "main.py"
15+
"--directory",
16+
"../src/", # change with the path to the MCP server
17+
"run",
18+
"main.py",
1719
],
18-
"env": {
19-
"REDIS_HOST": "127.0.0.1",
20-
"REDIS_PORT": "6379",
21-
"REDIS_USERNAME": "default",
22-
"REDIS_PWD": ""
23-
},
20+
"env": {
21+
"REDIS_HOST": "127.0.0.1",
22+
"REDIS_PORT": "6379",
23+
"REDIS_USERNAME": "default",
24+
"REDIS_PWD": "",
25+
},
2426
}
2527
)
2628

@@ -30,7 +32,7 @@ async def build_agent():
3032
agent = Agent(
3133
name="Redis Assistant",
3234
instructions="You are a helpful assistant capable of reading and writing to Redis. Store every question and answer in the Redis Stream app:logger",
33-
mcp_servers=[server]
35+
mcp_servers=[server],
3436
)
3537

3638
return agent
@@ -46,7 +48,7 @@ async def cli(agent, max_history=30):
4648
if q.strip().lower() in {"exit", "quit"}:
4749
break
4850

49-
if (len(q.strip()) > 0):
51+
if len(q.strip()) > 0:
5052
# Format the context into a single string
5153
history = ""
5254
for turn in conversation_history:
@@ -58,7 +60,9 @@ async def cli(agent, max_history=30):
5860

5961
response_text = ""
6062
async for event in result.stream_events():
61-
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
63+
if event.type == "raw_response_event" and isinstance(
64+
event.data, ResponseTextDeltaEvent
65+
):
6266
print(event.data.delta, end="", flush=True)
6367
response_text += event.data.delta
6468
print("\n")

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: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
# Log at DEBUG to avoid noisy stderr while still providing diagnostics.
50+
logging.getLogger(__name__).debug(
51+
"Failed to adjust handler level for handler %r", h, exc_info=True
52+
)
53+
54+
# Only add our own stderr handler if there are NO handlers at all.
55+
# Many hosts (pytest, uv, VS Code) install a console handler already.
56+
if not root.handlers:
57+
sh = logging.StreamHandler(sys.stderr)
58+
sh.setLevel(level)
59+
sh.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
60+
root.addHandler(sh)
61+
62+
# Route warnings.warn(...) through logging
63+
logging.captureWarnings(True)
64+
65+
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()

0 commit comments

Comments
 (0)