Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The Redis MCP Server is a **natural language interface** designed for agentic ap
- [Redis ACL](#redis-acl)
- [Configuration via command line arguments](#configuration-via-command-line-arguments)
- [Configuration via Environment Variables](#configuration-via-environment-variables)
- [Logging](#logging)
- [Integrations](#integrations)
- [OpenAI Agents SDK](#openai-agents-sdk)
- [Augment](#augment)
Expand Down Expand Up @@ -78,7 +79,7 @@ Additional tools.

## Installation

The Redis MCP Server is available as a PyPI package and as direct installation from the GitHub repository.
The Redis MCP Server is available as a PyPI package and as direct installation from the GitHub repository.

### From PyPI (recommended)
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.
Expand Down Expand Up @@ -125,7 +126,7 @@ However, starting the MCP Server is most useful when delegate to the framework o

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).

> It is recommended to use a tagged release, the `main` branch is under active development and may contain breaking changes.
> It is recommended to use a tagged release, the `main` branch is under active development and may contain breaking changes.

As an example, you can execute the following command to run the `0.2.0` release:

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

# Run with Redis URI and SSL
# Run with Redis URI and SSL
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>"

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

There are several ways to set environment variables:

1. **Using a `.env` File**:
1. **Using a `.env` File**:
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`).
For example, create a `.env` file with the following content from the `.env.example` file provided in the repository:

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

OR,

2. **Setting Variables in the Shell**:
2. **Setting Variables in the Shell**:
You can export environment variables directly in your shell before running your application. For example:

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


### Logging

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.

- Accepted values (case-insensitive): `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`, `NOTSET`
- Aliases supported: `WARN` → `WARNING`, `FATAL` → `CRITICAL`
- Numeric values are also accepted, including signed (e.g., `"10"`, `"+20"`)
- Default when unset or unrecognized: `WARNING`

Handler behavior
- 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.
- If no handlers are present, the server adds a single stderr StreamHandler with a simple format.

Examples
```bash
# See normal lifecycle messages
MCP_REDIS_LOG_LEVEL=INFO uv run src/main.py

# Very verbose for debugging
MCP_REDIS_LOG_LEVEL=DEBUG uvx --from redis-mcp-server@latest redis-mcp-server --url redis://localhost:6379/0
```

In MCP client configs that support env, add it alongside your Redis settings. For example:
```json
{
"mcpServers": {
"redis": {
"command": "uvx",
"args": ["--from", "redis-mcp-server@latest", "redis-mcp-server", "--url", "redis://localhost:6379/0"],
"env": {
"REDIS_HOST": "localhost",
"REDIS_PORT": "6379",
"MCP_REDIS_LOG_LEVEL": "INFO"
}
}
}
}
```


## Integrations

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.
Expand Down
26 changes: 15 additions & 11 deletions examples/redis_assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ async def build_agent():
params={
"command": "uv",
"args": [
"--directory", "../src/", # change with the path to the MCP server
"run", "main.py"
"--directory",
"../src/", # change with the path to the MCP server
"run",
"main.py",
],
"env": {
"REDIS_HOST": "127.0.0.1",
"REDIS_PORT": "6379",
"REDIS_USERNAME": "default",
"REDIS_PWD": ""
},
"env": {
"REDIS_HOST": "127.0.0.1",
"REDIS_PORT": "6379",
"REDIS_USERNAME": "default",
"REDIS_PWD": "",
},
}
)

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

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

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

response_text = ""
async for event in result.stream_events():
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
if event.type == "raw_response_event" and isinstance(
event.data, ResponseTextDeltaEvent
):
print(event.data.delta, end="", flush=True)
response_text += event.data.delta
print("\n")
Expand Down
18 changes: 10 additions & 8 deletions src/common/connection.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import sys
import logging
from typing import Optional, Type, Union

import redis
Expand All @@ -8,6 +8,8 @@
from src.common.config import REDIS_CFG
from src.version import __version__

_logger = logging.getLogger(__name__)


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

except redis.exceptions.ConnectionError:
print("Failed to connect to Redis server", file=sys.stderr)
_logger.error("Failed to connect to Redis server")
raise
except redis.exceptions.AuthenticationError:
print("Authentication failed", file=sys.stderr)
_logger.error("Authentication failed")
raise
except redis.exceptions.TimeoutError:
print("Connection timed out", file=sys.stderr)
_logger.error("Connection timed out")
raise
except redis.exceptions.ResponseError as e:
print(f"Response error: {e}", file=sys.stderr)
_logger.error("Response error: %s", e)
raise
except redis.exceptions.RedisError as e:
print(f"Redis error: {e}", file=sys.stderr)
_logger.error("Redis error: %s", e)
raise
except redis.exceptions.ClusterError as e:
print(f"Redis Cluster error: {e}", file=sys.stderr)
_logger.error("Redis Cluster error: %s", e)
raise
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
_logger.error("Unexpected error: %s", e)
raise

return cls._instance
65 changes: 65 additions & 0 deletions src/common/logging_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import logging
import os
import sys


def resolve_log_level() -> int:
"""Resolve desired log level from MCP_REDIS_LOG_LEVEL.

Accepts numeric strings or standard level names (DEBUG, INFO, WARNING,
ERROR, CRITICAL, NOTSET) including aliases WARN and FATAL. Defaults to WARNING.
"""
name = os.getenv("MCP_REDIS_LOG_LEVEL")
if name:
s = name.strip()
try:
return int(s)
except ValueError:
pass
level = getattr(logging, s.upper(), None)
if isinstance(level, int):
return level
return logging.WARNING


def configure_logging() -> int:
"""Configure logging based on environment.

- Default level WARNING
- MCP_REDIS_LOG_LEVEL to override

Returns the resolved log level. Idempotent.
"""

level = resolve_log_level()
root = logging.getLogger()

# Always set the root logger level
root.setLevel(level)

# Only lower overly-restrictive handler thresholds to avoid host filtering.
# - Leave NOTSET (0) alone so it defers to logger/root levels
# - Do not raise handler thresholds (respect host-configured verbosity)
for h in root.handlers:
try:
cur = getattr(h, "level", None)
if isinstance(cur, int) and cur != logging.NOTSET and cur > level:
h.setLevel(level)
except Exception:
# Log at DEBUG to avoid noisy stderr while still providing diagnostics.
logging.getLogger(__name__).debug(
"Failed to adjust handler level for handler %r", h, exc_info=True
)

# Only add our own stderr handler if there are NO handlers at all.
# Many hosts (pytest, uv, VS Code) install a console handler already.
if not root.handlers:
sh = logging.StreamHandler(sys.stderr)
sh.setLevel(level)
sh.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
root.addHandler(sh)

# Route warnings.warn(...) through logging
logging.captureWarnings(True)

return level
7 changes: 6 additions & 1 deletion src/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import sys
import logging

import click

from src.common.config import parse_redis_uri, set_redis_config_from_cli
from src.common.server import mcp
from src.common.logging_utils import configure_logging


class RedisMCPServer:
def __init__(self):
print("Starting the Redis MCP Server", file=sys.stderr)
# Configure logging on server initialization (idempotent)
configure_logging()
self._logger = logging.getLogger(__name__)
self._logger.info("Starting the Redis MCP Server")

def run(self):
mcp.run()
Expand Down
Loading
Loading