Skip to content

refactor: consolidate duplicated tool/prompt logic into entityRegistry#1148

Open
rogueslasher wants to merge 2 commits into
Kuadrant:mainfrom
rogueslasher:feat/registry-generic-helper
Open

refactor: consolidate duplicated tool/prompt logic into entityRegistry#1148
rogueslasher wants to merge 2 commits into
Kuadrant:mainfrom
rogueslasher:feat/registry-generic-helper

Conversation

@rogueslasher

@rogueslasher rogueslasher commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Refactors the upstream manager by extracting a generic entityRegistry to eliminate duplicated tool and prompt management logic. Previously, tools and prompts had separate implementations for several operations despite sharing nearly identical behavior. This change consolidates that logic into a single reusable registry abstraction while preserving the existing public API and behavior.

Changes

  • Added internal/broker/upstream/registry.go containing the new generic entityRegistry.
  • Updated MCPManager to use separate tool and prompt registries backed by the shared implementation.

closes #1103

Summary by CodeRabbit

  • Refactor
    • Unified upstream tool and prompt tracking and synchronization, using a shared registry approach to keep gateway state consistent and reduce duplicated logic.
  • Bug Fixes
    • Improved cleanup of gateway registrations when upstream servers are disabled or become unreachable (e.g., connect/ping failures).
  • Tests
    • Updated unit tests to match the new unified tool/prompt management behavior and assertions.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dc17c884-fcdd-4337-9d84-5ef2e9d3deaa

📥 Commits

Reviewing files that changed from the base of the PR and between 57a83d6 and 05ac2d9.

📒 Files selected for processing (1)
  • internal/broker/upstream/registry.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/broker/upstream/registry.go

📝 Walkthrough

Walkthrough

Introduces entityRegistry[E, S], a generic struct consolidating duplicated tool/prompt lifecycle logic (diff, conflict detection, remove-all, get, served-lookup, test seeding). MCPManager struct fields and all internal operations are rewired to use two registry instances; legacy helper methods are deleted. Tests are updated to call registry methods directly.

Changes

Generic entityRegistry and MCPManager refactor

Layer / File(s) Summary
Generic entityRegistry type and core methods
internal/broker/upstream/registry.go
Defines entityRegistry[E,S] with injected function pointers and implements toServerItem, diff, findConflicts, removeAll, get, getManagedCopy, getServed, and setForTesting.
Tool and prompt registry constructors
internal/broker/upstream/registry.go
Adds newToolRegistry and newPromptRegistry that specialize the generic registry with name prefixing, gateway server ID stamping, non-forwarding handler stubs, and wired gateway add/delete/list operations.
MCPManager struct and constructor wiring
internal/broker/upstream/manager.go
Replaces per-type slices/maps with tools and prompts registry fields; NewUpstreamMCPManager initializes them via the new constructors with upstream list callbacks; imports updated.
Lifecycle and sync loop updates
internal/broker/upstream/manager.go
Shutdown, disabled/failed/ping-failed paths, tool/prompt fetch-diff-update blocks, and shouldFetchTools/shouldFetchPrompts all switch from legacy helpers to registry method calls.
Public query and testing method delegation
internal/broker/upstream/manager.go
GetManagedTools, GetServedManagedTool, SetToolsForTesting, GetManagedPrompts, GetServedManagedPrompt, SetPromptsForTesting delegate to the respective registries.
Test updates
internal/broker/upstream/manager_test.go
State setup and assertions switch from old manager.serverTools, manager.toolToServerTool, manager.diffTools, manager.promptToServerPrompt, manager.diffPrompts to manager.tools.* / manager.prompts.*.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Kuadrant/mcp-gateway#943: Overlaps at MCPManager.manage() internals — tool state bookkeeping, diffs, and gateway add/remove behavior in the same file.
  • Kuadrant/mcp-gateway#886: Both modify conflict detection and removal logic in manager.go's tool/prompt sync paths.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main refactoring: consolidating duplicated tool/prompt logic into a shared entityRegistry abstraction.
Linked Issues check ✅ Passed All code requirements from #1103 are met: duplicated logic across eight method pairs is consolidated into generic entityRegistry, no behavior changes, and existing tests are updated accordingly.
Out of Scope Changes check ✅ Passed All changes are directly aligned with #1103 objectives. The refactoring consolidates the specified duplicated method pairs and updates related test assertions accordingly.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the review-effort/large High review effort (4-5): many files, complex, cross-cutting label Jun 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
internal/broker/upstream/registry.go (1)

109-112: 💤 Low value

Wasteful toServer call for removals.

toServer builds a full server item (mutates name, allocates meta, creates handler closure) just to extract the prefixed name. Consider adding a getPrefixedName func(E) string to avoid the allocation overhead on removals.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/broker/upstream/registry.go` around lines 109 - 112, The removal
loop in the registry.go file calls `r.toServer(item)` just to extract the
prefixed name via `r.getServerName()`, which is wasteful since `toServer`
performs full server item construction (name mutation, meta allocation, handler
closure creation). Add a new method `getPrefixedName(item E) string` that
directly extracts the prefixed name from an item without the overhead of full
conversion, then replace the `r.getServerName(r.toServer(item))` call in the
removal loop with `r.getPrefixedName(item)` to eliminate unnecessary
allocations.
internal/broker/upstream/manager_test.go (1)

381-381: ⚡ Quick win

Consider adding a test helper for serverItems access.

Tests directly manipulate and read the unexported serverItems field. While legal within the same package, this couples tests to registry internals. A GetServerItemsForTesting() method on entityRegistry would improve encapsulation and maintainability.

Suggested test helper pattern

Add to entityRegistry in registry.go:

+// GetServerItemsForTesting returns a copy of serverItems for test verification
+func (r *entityRegistry[E, S]) GetServerItemsForTesting() []S {
+	return append([]S(nil), r.serverItems...)
+}

Then update tests:

-manager.tools.serverItems = make([]server.ServerTool, tc.numServerTools)
+manager.tools.SetServerItemsForTesting(make([]server.ServerTool, tc.numServerTools))

-manager.tools.serverItems = []server.ServerTool{{Tool: mcp.Tool{Name: "existing_tool"}}}
+manager.tools.SetServerItemsForTesting([]server.ServerTool{{Tool: mcp.Tool{Name: "existing_tool"}}})

-for i, st := range manager.tools.serverItems {
+for i, st := range manager.tools.GetServerItemsForTesting() {

Also applies to: 758-758, 962-963

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/broker/upstream/manager_test.go` at line 381, Tests directly access
the unexported serverItems field on the manager, coupling the tests to internal
implementation details. Add a GetServerItemsForTesting() method to the
entityRegistry type in registry.go that returns the serverItems field for
testing purposes. Then update all three locations where serverItems is accessed
in manager_test.go (at line 381, line 758, and lines 962-963) to use this new
test helper method instead of directly reading or manipulating the unexported
field.
internal/broker/upstream/manager.go (1)

400-414: ⚖️ Poor tradeoff

Registry state update could be encapsulated.

Direct manipulation of registry fields (items, byName, byServedName, serverItems) breaks abstraction. The same pattern repeats for prompts (lines 455-467). Consider adding an applyDiff or update method to entityRegistry that handles state transitions internally.

This would reduce the duplication between the tools and prompts update blocks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/broker/upstream/manager.go` around lines 400 - 414, The code
directly manipulates entityRegistry fields (items, byName, byServedName,
serverItems) within the manager's lock/unlock blocks, breaking encapsulation.
Add an applyDiff or update method to the entityRegistry type that encapsulates
all state transitions internally (recreating byName and byServedName maps,
applying deletions with slices.DeleteFunc, and appending new items). Call this
new method from the manager code instead of directly manipulating the registry
fields. Apply this refactoring to both the tools update block and the prompts
update block to eliminate the duplication between them.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/broker/upstream/manager.go`:
- Around line 127-128: The comment for the toolsLock sync.RWMutex contains stale
references to serverTools and serverPrompts which were removed during
refactoring to registry-based fields. Update the comment to accurately reflect
which fields are now actually protected by this lock, removing the outdated
field names and replacing them with the current registry-based field names that
toolsLock guards.

In `@internal/broker/upstream/registry.go`:
- Line 1: The file has Go formatting violations detected by gofmt. Run the `make
fmt` command to automatically reformat the file according to Go style standards.
This will fix all formatting issues in the package upstream file.
- Around line 128-129: The loop iterating over gatewayItems does not validate
that existingPtr is non-nil before dereferencing it when calling
r.getMetaFields(*existingPtr). Add a nil check for existingPtr before
dereferencing it to prevent a panic if listFromGateway() returns map entries
with nil pointer values. Skip processing or handle the case appropriately when
existingPtr is nil within the for loop that iterates over existingName and
existingPtr from gatewayItems.

---

Nitpick comments:
In `@internal/broker/upstream/manager_test.go`:
- Line 381: Tests directly access the unexported serverItems field on the
manager, coupling the tests to internal implementation details. Add a
GetServerItemsForTesting() method to the entityRegistry type in registry.go that
returns the serverItems field for testing purposes. Then update all three
locations where serverItems is accessed in manager_test.go (at line 381, line
758, and lines 962-963) to use this new test helper method instead of directly
reading or manipulating the unexported field.

In `@internal/broker/upstream/manager.go`:
- Around line 400-414: The code directly manipulates entityRegistry fields
(items, byName, byServedName, serverItems) within the manager's lock/unlock
blocks, breaking encapsulation. Add an applyDiff or update method to the
entityRegistry type that encapsulates all state transitions internally
(recreating byName and byServedName maps, applying deletions with
slices.DeleteFunc, and appending new items). Call this new method from the
manager code instead of directly manipulating the registry fields. Apply this
refactoring to both the tools update block and the prompts update block to
eliminate the duplication between them.

In `@internal/broker/upstream/registry.go`:
- Around line 109-112: The removal loop in the registry.go file calls
`r.toServer(item)` just to extract the prefixed name via `r.getServerName()`,
which is wasteful since `toServer` performs full server item construction (name
mutation, meta allocation, handler closure creation). Add a new method
`getPrefixedName(item E) string` that directly extracts the prefixed name from
an item without the overhead of full conversion, then replace the
`r.getServerName(r.toServer(item))` call in the removal loop with
`r.getPrefixedName(item)` to eliminate unnecessary allocations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b004d583-9c82-4347-b1b7-17c97221aee6

📥 Commits

Reviewing files that changed from the base of the PR and between e99c0a6 and 1d1240f.

📒 Files selected for processing (3)
  • internal/broker/upstream/manager.go
  • internal/broker/upstream/manager_test.go
  • internal/broker/upstream/registry.go

Comment thread internal/broker/upstream/manager.go
Comment thread internal/broker/upstream/registry.go Outdated
Comment thread internal/broker/upstream/registry.go Outdated
@rogueslasher rogueslasher marked this pull request as draft June 17, 2026 11:14
Signed-off-by: rogueslasher <aniketpandey25092005@gmail.com>
@rogueslasher rogueslasher force-pushed the feat/registry-generic-helper branch from 1d1240f to 57a83d6 Compare June 18, 2026 13:39
@rogueslasher

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added the high-risk Touches concurrency, auth, sessions, CRDs, ext_proc, or routing label Jun 19, 2026
Signed-off-by: rogueslasher <aniketpandey25092005@gmail.com>
@rogueslasher

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@rogueslasher

Copy link
Copy Markdown
Contributor Author

@maleck13 please review this one , thank you

@Aman-Cool

Copy link
Copy Markdown
Contributor

@rogueslasher,Read this with my guard up... it's a refactor of the federation state, so all that matters is whether it preserves behaviour. Went looking for a dropped guard and couldn't find one: the generic findConflicts keeps the same name + server-ID check, and it's still one toolsLock over both registries with removeAll releasing before the gateway call. Tidy.

One small thing: in diff, the remove path builds a whole ServerTool just to read its prefixed name, where the old code did a plain string concat.., minor, but cheap to prefix directly given how allocation-conscious the broker path is.

Nice cleanup... the generics read well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

high-risk Touches concurrency, auth, sessions, CRDs, ext_proc, or routing review-effort/large High review effort (4-5): many files, complex, cross-cutting triage/has-issue PR links to an existing issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reduce duplicated tool/prompt code in MCPManager

3 participants