Skip to content

Fix/redis sentinel credentials#320

Merged
iFurySt merged 2 commits into
AmoyLab:mainfrom
kx-byte:fix/redis-sentinel-credentials
Jun 8, 2026
Merged

Fix/redis sentinel credentials#320
iFurySt merged 2 commits into
AmoyLab:mainfrom
kx-byte:fix/redis-sentinel-credentials

Conversation

@kx-byte

@kx-byte kx-byte commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Add support for Redis Sentinel credentials across notifier, session, and OAuth2 Redis clients and ensure MCP gateway configuration loads them correctly.

New Features:

  • Allow configuring Redis Sentinel username and password for notifier, session store, and OAuth2 storage via configuration and environment variables.

Bug Fixes:

  • Ensure Redis Sentinel connections use dedicated Sentinel credentials instead of the primary Redis username/password.
  • Adjust Redis notifier stream watch logic to start from new messages only, avoiding reprocessing existing entries.

Enhancements:

  • Extend Redis notifier and auth storage constructors and factories to propagate Sentinel credentials from configuration.

Documentation:

  • Document Sentinel username and password options in the MCP gateway Redis-related configuration.

Tests:

  • Add configuration loading test to verify Redis Sentinel credential interpolation in the MCP gateway config.
  • Update Redis notifier and auth storage tests to cover the extended constructors while maintaining existing behaviors.

@sourcery-ai

sourcery-ai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds support for Redis Sentinel authentication credentials across notifier, session, and OAuth2 storage components, wires them through configuration and factories, and adjusts Redis stream watching behavior to start from new messages only.

File-Level Changes

Change Details Files
Extend Redis-related configs to carry Sentinel-specific credentials and expose them in the default mcp-gateway configuration.
  • Add SentinelUsername and SentinelPassword fields to notifier RedisConfig, session SessionRedisConfig, and OAuth2RedisConfig structs.
  • Update configs/mcp-gateway.yaml to define sentinel_username and sentinel_password entries for notifier, session, and OAuth2 Redis sections, sourced from new environment variables.
  • Introduce a config test that verifies MCPGatewayConfig correctly loads Sentinel credentials from environment-driven YAML.
internal/common/config/notifier.go
internal/common/config/config.go
configs/mcp-gateway.yaml
internal/common/config/config_test.go
Propagate Sentinel credentials into Redis clients for notifier, session store, and OAuth2 storage, updating constructors, factories, and tests.
  • Update NewRedisNotifier and NewRedisStorage signatures to accept sentinel username/password and assign them to redis.UniversalOptions when using Sentinel cluster type.
  • Wire new Sentinel credential fields through notifier and auth storage factories so callers use config-backed values.
  • Adjust notifier and Redis storage tests to call updated constructors with placeholder Sentinel credentials.
  • Update session Redis store creation to set SentinelUsername and SentinelPassword on redis.UniversalOptions when configured.
internal/mcp/storage/notifier/redis.go
internal/auth/storage/redis.go
internal/mcp/storage/notifier/factory.go
internal/auth/storage/factory.go
internal/mcp/storage/notifier/redis_test.go
internal/auth/storage/redis_test.go
internal/mcp/session/redis.go
Change Redis notifier stream watch behavior to only consume new messages going forward instead of replaying existing history.
  • Remove the initial XRevRangeN query and associated logic that captured the current tail ID before starting the watch loop.
  • Initialize the stream read ID to "$" so XRead blocks only for new messages published after Watch starts.
internal/mcp/storage/notifier/redis.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The function signatures for NewRedisNotifier and NewRedisStorage are getting quite long with the added sentinel credentials; consider introducing a small options/config struct to group these parameters and make call sites easier to read and maintain.
  • In RedisNotifier.Watch, changing lastID from the previous tail (via XRevRangeN) to "$" alters behavior to only receive new messages and not the latest existing one; if this is intended, it might be worth double-checking all callers rely on this semantics and don’t expect to catch the most recent configuration on startup.
  • In TestLoadConfig_MCPGateway_RedisSentinelCredentials, the if err != nil { return } after assert.NoError(t, err) is redundant and can be removed since the assertion already fails the test in that case.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The function signatures for `NewRedisNotifier` and `NewRedisStorage` are getting quite long with the added sentinel credentials; consider introducing a small options/config struct to group these parameters and make call sites easier to read and maintain.
- In `RedisNotifier.Watch`, changing `lastID` from the previous tail (via `XRevRangeN`) to `"$"` alters behavior to only receive new messages and not the latest existing one; if this is intended, it might be worth double-checking all callers rely on this semantics and don’t expect to catch the most recent configuration on startup.
- In `TestLoadConfig_MCPGateway_RedisSentinelCredentials`, the `if err != nil { return }` after `assert.NoError(t, err)` is redundant and can be removed since the assertion already fails the test in that case.

## Individual Comments

### Comment 1
<location path="internal/mcp/storage/notifier/redis.go" line_range="72" />
<code_context>
 	go func() {
 		defer close(ch)

+		// Start from the latest message ($ means read only new messages)
+		lastID := "$"
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Changing `lastID` to `$` alters notifier semantics and may skip the latest config on startup.

With the previous `XRevRangeN(..., 1)` approach, `Watch` ensured consumers saw the most recent config (or at least didn’t miss updates that happened right before subscription). Using `lastID := "$"` means subscribers now only see messages after `Watch` starts and never get the current tail, which can leave them on a stale config until the next update. If you still want to avoid replaying history but deliver the current tail, consider keeping the initial `XRevRangeN` and seeding `lastID` with that ID before `XRead`, or verify that all callers are OK with no initial config snapshot.
</issue_to_address>

### Comment 2
<location path="internal/common/config/config_test.go" line_range="112-114" />
<code_context>
+	file := filepath.Join(tmp, "mcp-gateway.yaml")
+	assert.NoError(t, os.WriteFile(file, []byte(yaml), 0o644))
+
+	cfg, _, err := LoadConfig[MCPGatewayConfig]("mcp-gateway.yaml")
+	assert.NoError(t, err)
+	if err != nil {
+		return
+	}
</code_context>
<issue_to_address>
**nitpick:** Use `require.NoError` or drop the redundant `if err != nil` in this test

After `assert.NoError(t, err)`, the `if err != nil { return }` branch is dead code in a passing test. Either use `require.NoError(t, err)` and drop the `if`, or keep `assert` and remove the `if` block to simplify the control flow and avoid implying early-return is part of the behavior under test.
</issue_to_address>

### Comment 3
<location path="internal/mcp/storage/notifier/redis_test.go" line_range="39" />
<code_context>
 	stream := "unla:mcp:updates"

-	recv, err := NewRedisNotifier(logger, cnst.RedisClusterTypeSingle, mr.Addr(), "", "", "", 0, stream, config.RoleReceiver)
+	recv, err := NewRedisNotifier(logger, cnst.RedisClusterTypeSingle, mr.Addr(), "", "", "", "", "", 0, stream, config.RoleReceiver)
 	assert.NoError(t, err)
 	assert.NotNil(t, recv)
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test to capture the new watch behavior (only new messages after subscription)

Since `Watch` now starts from `$`, it should only receive messages published after subscription. We already cover the happy path where the message is published after `Watch`, but we’re missing a test that asserts messages published before `Watch` are not delivered. Please add a test that:

1. Publishes a message to the stream
2. Calls `Watch` and reads from the channel for a bit
3. Asserts that the earlier message is never emitted

This will lock in the new offset semantics and prevent regressions.

Suggested implementation:

```golang
	logger := zap.NewNop()
	stream := "unla:mcp:updates"

	recv, err := NewRedisNotifier(logger, cnst.RedisClusterTypeSingle, mr.Addr(), "", "", "", "", "", 0, stream, config.RoleReceiver)
	assert.NoError(t, err)
	assert.NotNil(t, recv)

	assert.NotNil(t, ch)

	// Create a sender and push an update
	send, err := NewRedisNotifier(logger, cnst.RedisClusterTypeSingle, mr.Addr(), "", "", "", "", "", 0, stream, config.RoleSender)
	assert.NoError(t, err)
	cfg := &config.MCPConfig{Name: "cfg-1"}
	assert.NoError(t, send.NotifyUpdate(context.Background(), cfg))
	}

func TestRedisNotifierWatchIgnoresExistingMessages(t *testing.T) {
	mr, err := miniredis.Run()
	require.NoError(t, err)
	defer mr.Close()

	logger := zap.NewNop()
	stream := "unla:mcp:updates"

	// Create a sender and publish a message *before* starting Watch.
	sender, err := NewRedisNotifier(logger, cnst.RedisClusterTypeSingle, mr.Addr(), "", "", "", "", "", 0, stream, config.RoleSender)
	assert.NoError(t, err)

	cfgBefore := &config.MCPConfig{Name: "cfg-before-watch"}
	assert.NoError(t, sender.NotifyUpdate(context.Background(), cfgBefore))

	// Now create the receiver and start Watch, which should start from `$`
	recv, err := NewRedisNotifier(logger, cnst.RedisClusterTypeSingle, mr.Addr(), "", "", "", "", "", 0, stream, config.RoleReceiver)
	assert.NoError(t, err)
	assert.NotNil(t, recv)

	ch, err := recv.Watch(context.Background())
	assert.NoError(t, err)
	assert.NotNil(t, ch)

	// Ensure that no message is delivered from the earlier publish.
	select {
	case msg := <-ch:
		assert.Failf(t, "expected no messages from Watch for existing entries", "got: %#v", msg)
	case <-time.After(100 * time.Millisecond):
		// Expected path: nothing delivered for pre-subscription messages.
	}
}

```

1. Ensure the test file imports the dependencies used in the new test:
   - `github.com/alicebob/miniredis/v2` as `miniredis` if not already present.
   - `github.com/stretchr/testify/require` as `require` if not already present.
   - The standard library `time` package for `time.After`.
2. The `recv.Watch` call assumes the existing signature `Watch(ctx context.Context) (<-chan *config.MCPConfig, error)`; if the signature differs, update the call in the new test to match the existing tests.
3. If there is a shared helper for setting up `miniredis` or notifiers in this test file, you may want to refactor the new test to reuse that helper instead of duplicating setup logic.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread internal/mcp/storage/notifier/redis.go
Comment thread internal/common/config/config_test.go Outdated
Comment thread internal/mcp/storage/notifier/redis_test.go
@kx-byte kx-byte force-pushed the fix/redis-sentinel-credentials branch from 884b4fd to 043019f Compare June 8, 2026 04:10
@iFurySt iFurySt merged commit 96f0bb6 into AmoyLab:main Jun 8, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants